Merge "Adding ArchiveManager skeleton with some basic validations"
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index fcff581..45bb161 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -21,10 +21,6 @@
"soong_zip",
],
- tool_files: [
- ":libprotobuf-internal-protos",
- ],
-
cmd: "mkdir -p $(genDir)/$(in) " +
"&& $(location aprotoc) " +
" --plugin=$(location protoc-gen-javastream) " +
@@ -42,6 +38,11 @@
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
+
+ data: [
+ ":libprotobuf-internal-protos",
+ ],
+
output_extension: "srcjar",
}
@@ -53,10 +54,6 @@
"protoc-gen-cppstream",
],
- tool_files: [
- ":libprotobuf-internal-protos",
- ],
-
cmd: "mkdir -p $(genDir) " +
"&& $(location aprotoc) " +
" --plugin=$(location protoc-gen-cppstream) " +
@@ -73,6 +70,10 @@
"libs/incident/**/*.proto",
],
+ data: [
+ ":libprotobuf-internal-protos",
+ ],
+
output_extension: "proto.h",
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index fd8ddbc..6c8af39 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -59,6 +59,10 @@
*/
void reportAppUsage(String packageName, int userId);
+ /** @return {@code true} if the app is considered buggy from JobScheduler's perspective. */
+ boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
+ int timeoutBlameUserId, @NonNull String timeoutBlamePackageName);
+
/**
* @return {@code true} if the given notification is associated with any user-initiated jobs.
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index 8a5d094..0717070 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -123,21 +123,21 @@
if (oldDetails == null) {
if (jobStatus.startedAsUserInitiatedJob) {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_initial_setNotification_call_required",
+ "job_scheduler.value_cntr_w_uid_initial_set_notification_call_required",
jobStatus.getUid());
} else {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_initial_setNotification_call_optional",
+ "job_scheduler.value_cntr_w_uid_initial_set_notification_call_optional",
jobStatus.getUid());
}
} else {
if (jobStatus.startedAsUserInitiatedJob) {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_required",
+ "job_scheduler.value_cntr_w_uid_subsequent_set_notification_call_required",
jobStatus.getUid());
} else {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_optional",
+ "job_scheduler.value_cntr_w_uid_subsequent_set_notification_call_optional",
jobStatus.getUid());
}
if (oldDetails.notificationId != notificationId) {
@@ -145,7 +145,7 @@
removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
jobStatus);
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_setNotification_changed_notification_ids",
+ "job_scheduler.value_cntr_w_uid_set_notification_changed_notification_ids",
jobStatus.getUid());
}
}
@@ -200,7 +200,10 @@
// No more jobs using this notification. Apply the final job stop policy.
// If the user attempted to stop the job/app, then always remove the notification
// so the user doesn't get confused about the app state.
+ // Similarly, if the user background restricted the app, remove the notification so
+ // the user doesn't think the app is continuing to run in the background.
if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE
+ || stopReason == JobParameters.STOP_REASON_BACKGROUND_RESTRICTION
|| stopReason == JobParameters.STOP_REASON_USER) {
mNotificationManagerInternal.cancelNotification(
packageName, packageName, details.appUid, details.appPid, /* tag */ null,
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 cbc9263..f99bcf1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -322,16 +322,25 @@
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
".schedulePersisted out-of-quota logged";
+ private static final String QUOTA_TRACKER_TIMEOUT_UIJ_TAG = "timeout-uij";
+ private static final String QUOTA_TRACKER_TIMEOUT_EJ_TAG = "timeout-ej";
+ private static final String QUOTA_TRACKER_TIMEOUT_REG_TAG = "timeout-reg";
+ private static final String QUOTA_TRACKER_TIMEOUT_TOTAL_TAG = "timeout-total";
+ private static final String QUOTA_TRACKER_ANR_TAG = "anr";
private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category(
".schedulePersisted()");
private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category(
".schedulePersisted out-of-quota logged");
- private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> {
- if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
- return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED;
- }
- return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED;
- };
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ =
+ new Category(QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ =
+ new Category(QUOTA_TRACKER_TIMEOUT_EJ_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_REG =
+ new Category(QUOTA_TRACKER_TIMEOUT_REG_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL =
+ new Category(QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_ANR = new Category(QUOTA_TRACKER_ANR_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_DISABLED = new Category("disabled");
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -493,10 +502,18 @@
}
switch (name) {
case Constants.KEY_ENABLE_API_QUOTAS:
+ case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
case Constants.KEY_API_QUOTA_SCHEDULE_COUNT:
case Constants.KEY_API_QUOTA_SCHEDULE_WINDOW_MS:
case Constants.KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT:
case Constants.KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS:
if (!apiQuotaScheduleUpdated) {
mConstants.updateApiQuotaConstantsLocked();
updateQuotaTracker();
@@ -583,10 +600,26 @@
@VisibleForTesting
void updateQuotaTracker() {
- mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+ mQuotaTracker.setEnabled(
+ mConstants.ENABLE_API_QUOTAS || mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
mConstants.API_QUOTA_SCHEDULE_COUNT,
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_REG,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_ANR,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
}
/**
@@ -616,6 +649,8 @@
"conn_low_signal_strength_relax_frac";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
+ // This has been enabled for 3+ full releases. We're unlikely to disable it.
+ // TODO(141645789): remove this flag
private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -623,6 +658,22 @@
"aq_schedule_throw_exception";
private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
"aq_schedule_return_failure";
+ private static final String KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC =
+ "enable_execution_safeguards_udc";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
+ "es_u_timeout_uij_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
+ "es_u_timeout_ej_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
+ "es_u_timeout_reg_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
+ "es_u_timeout_total_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+ "es_u_timeout_window_ms";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT =
+ "es_u_anr_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+ "es_u_anr_window_ms";
private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS =
"runtime_free_quota_max_limit_ms";
@@ -662,6 +713,17 @@
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+ private static final boolean DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ // EJs have a shorter timeout, so set a higher limit for them to start with.
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 5;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 3;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = 10;
+ private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+ 24 * HOUR_IN_MILLIS;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = 3;
+ private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+ 6 * HOUR_IN_MILLIS;
@VisibleForTesting
public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS;
@VisibleForTesting
@@ -774,6 +836,55 @@
public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
+ /**
+ * Whether to enable the execution safeguards added in UDC.
+ */
+ public boolean ENABLE_EXECUTION_SAFEGUARDS_UDC = DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC;
+ /**
+ * The maximum number of times an app can have a user-iniated job time out before the system
+ * begins removing some of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT;
+ /**
+ * The maximum number of times an app can have an expedited job time out before the system
+ * begins removing some of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT;
+ /**
+ * The maximum number of times an app can have a regular job time out before the system
+ * begins removing some of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT;
+ /**
+ * The maximum number of times an app can have jobs time out before the system
+ * attempts to restrict most of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT;
+ /**
+ * The time window that {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT},
+ * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT},
+ * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT}, and
+ * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT} should be evaluated over.
+ */
+ public long EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS;
+
+ /**
+ * The maximum number of times an app can ANR from JobScheduler's perspective before
+ * JobScheduler will attempt to restrict the app.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT;
+ /**
+ * The time window that {@link #EXECUTION_SAFEGUARDS_UDC_ANR_COUNT}
+ * should be evaluated over.
+ */
+ public long EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS;
+
/** The maximum amount of time we will let a job run for when quota is "free". */
public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
@@ -915,6 +1026,9 @@
private void updateApiQuotaConstantsLocked() {
ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
+ ENABLE_EXECUTION_SAFEGUARDS_UDC = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC);
// Set a minimum value on the quota limit so it's not so low that it interferes with
// legitimate use cases.
API_QUOTA_SCHEDULE_COUNT = Math.max(250,
@@ -931,6 +1045,40 @@
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
+
+ // Set a minimum value on the timeout limit so it's not so low that it interferes with
+ // legitimate use cases.
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = Math.max(2,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = Math.max(2,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = Math.max(2,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
+ final int highestTimeoutCount = Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = Math.max(highestTimeoutCount,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = Math.max(1,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
}
private void updateRuntimeConstantsLocked() {
@@ -1029,6 +1177,23 @@
pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();
+ pw.print(KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, ENABLE_EXECUTION_SAFEGUARDS_UDC)
+ .println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_ANR_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
+ EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS).println();
+
pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
@@ -2252,12 +2417,52 @@
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER);
- mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
- mConstants.API_QUOTA_SCHEDULE_COUNT,
- mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+
+ final Categorizer quotaCategorizer = (userId, packageName, tag) -> {
+ if (QUOTA_TRACKER_TIMEOUT_UIJ_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_TIMEOUT_EJ_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_TIMEOUT_REG_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_REG
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_TIMEOUT_TOTAL_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_ANR_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_ANR
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
+ return mConstants.ENABLE_API_QUOTAS
+ ? QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_SCHEDULE_LOGGED.equals(tag)) {
+ return mConstants.ENABLE_API_QUOTAS
+ ? QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ Slog.wtf(TAG, "Unexpected category tag: " + tag);
+ return QUOTA_TRACKER_CATEGORY_DISABLED;
+ };
+ mQuotaTracker = new CountQuotaTracker(context, quotaCategorizer);
+ updateQuotaTracker();
// Log at most once per minute.
+ // Set outside updateQuotaTracker() since this is intentionally not configurable.
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_DISABLED, Integer.MAX_VALUE, 60_000);
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
mAppStandbyInternal.addListener(mStandbyTracker);
@@ -2762,6 +2967,48 @@
0 /* Reset cumulativeExecutionTime because of successful execution */);
}
+ @VisibleForTesting
+ void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
+ boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ // If madeActive = 0, the job never actually started.
+ if (!jobTimedOut && jobStatus.madeActive > 0) {
+ final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
+ // The debug reason may be different if we stopped the job for some other reason
+ // (eg. constraints), so look at total execution time to be safe.
+ if (jobStatus.startedAsUserInitiatedJob) {
+ // TODO: factor in different min guarantees for different UI job types
+ jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ } else if (jobStatus.startedAsExpeditedJob) {
+ jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ } else {
+ jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ }
+ }
+ if (jobTimedOut) {
+ final int userId = jobStatus.getTimeoutBlameUserId();
+ final String pkg = jobStatus.getTimeoutBlamePackageName();
+ mQuotaTracker.noteEvent(userId, pkg,
+ jobStatus.startedAsUserInitiatedJob
+ ? QUOTA_TRACKER_TIMEOUT_UIJ_TAG
+ : (jobStatus.startedAsExpeditedJob
+ ? QUOTA_TRACKER_TIMEOUT_EJ_TAG
+ : QUOTA_TRACKER_TIMEOUT_REG_TAG));
+ if (!mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_TIMEOUT_TOTAL_TAG)) {
+ mAppStandbyInternal.restrictApp(
+ pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+ }
+ }
+
+ if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_ANR) {
+ final int callingUserId = jobStatus.getUserId();
+ final String callingPkg = jobStatus.getServiceComponent().getPackageName();
+ if (!mQuotaTracker.noteEvent(callingUserId, callingPkg, QUOTA_TRACKER_ANR_TAG)) {
+ mAppStandbyInternal.restrictApp(callingPkg, callingUserId,
+ UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+ }
+ }
+ }
+
// JobCompletedListener implementations.
/**
@@ -2784,6 +3031,8 @@
mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis();
mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY;
+ maybeProcessBuggyJob(jobStatus, debugStopReason);
+
if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_UNINSTALL
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED) {
// The job should have already been cleared from the rest of the JS tracking. No need
@@ -3511,26 +3760,36 @@
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
job.getSourceUid(), job.getSourcePackageName())) {
+ // The calling package is the one doing the work, so use it in the
+ // timeout quota checks.
+ final boolean isWithinTimeoutQuota = mQuotaTracker.isWithinQuota(
+ job.getTimeoutBlameUserId(), job.getTimeoutBlamePackageName(),
+ QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
+ final long upperLimitMs = isWithinTimeoutQuota
+ ? mConstants.RUNTIME_UI_LIMIT_MS
+ : mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
if (job.getJob().getRequiredNetwork() != null) {
// User-initiated data transfers.
if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) {
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
- return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
+ return Math.min(upperLimitMs,
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
}
// Try to give the job at least as much time as we think the transfer
// will take, but cap it at the maximum limit.
final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
* mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
- return Math.min(mConstants.RUNTIME_UI_LIMIT_MS,
+ return Math.min(upperLimitMs,
Math.max(factoredTransferTimeMs,
mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
- return Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
- mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
+ return Math.min(upperLimitMs,
+ Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
- return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ return Math.min(upperLimitMs, mConstants.RUNTIME_MIN_UI_GUARANTEE_MS);
} else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3547,13 +3806,24 @@
synchronized (mLock) {
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
- job.getSourceUid(), job.getSourcePackageName())) {
+ job.getSourceUid(), job.getSourcePackageName())
+ && mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
+ job.getTimeoutBlamePackageName(),
+ QUOTA_TRACKER_TIMEOUT_UIJ_TAG)) {
return mConstants.RUNTIME_UI_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
- return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ // Only let the app use the higher runtime if it hasn't repeatedly timed out.
+ final String timeoutTag = job.shouldTreatAsExpeditedJob()
+ ? QUOTA_TRACKER_TIMEOUT_EJ_TAG : QUOTA_TRACKER_TIMEOUT_REG_TAG;
+ final long upperLimitMs =
+ mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
+ job.getTimeoutBlamePackageName(), timeoutTag)
+ ? mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS
+ : mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ return Math.min(upperLimitMs,
mConstants.USE_TARE_POLICY
? mTareController.getMaxJobExecutionTimeMsLocked(job)
: mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -3797,6 +4067,17 @@
}
@Override
+ public boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
+ int timeoutBlameUserId, @NonNull String timeoutBlamePackageName) {
+ return !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
+ QUOTA_TRACKER_ANR_TAG)
+ || !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
+ QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)
+ || !mQuotaTracker.isWithinQuota(timeoutBlameUserId, timeoutBlamePackageName,
+ QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
+ }
+
+ @Override
public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
int userId, @NonNull String packageName) {
if (packageName == null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 0b08b6f..109686d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -413,16 +413,22 @@
final Intent intent = new Intent().setComponent(job.getServiceComponent())
.setFlags(Intent.FLAG_FROM_BACKGROUND);
boolean binding = false;
+ boolean startedWithForegroundFlag = false;
try {
final Context.BindServiceFlags bindFlags;
- if (job.shouldTreatAsUserInitiatedJob()) {
+ if (job.shouldTreatAsUserInitiatedJob() && !job.isUserBgRestricted()) {
+ // If the user has bg restricted the app, don't give the job FG privileges
+ // such as bypassing data saver or getting the higher foreground proc state.
+ // If we've gotten to this point, the app is most likely in the foreground,
+ // so the job will run just fine while the user keeps the app in the foreground.
bindFlags = Context.BindServiceFlags.of(
Context.BIND_AUTO_CREATE
| Context.BIND_ALMOST_PERCEPTIBLE
| Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
| Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS
| Context.BIND_NOT_APP_COMPONENT_USAGE);
- } else if (job.shouldTreatAsExpeditedJob()) {
+ startedWithForegroundFlag = true;
+ } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
bindFlags = Context.BindServiceFlags.of(
Context.BIND_AUTO_CREATE
| Context.BIND_NOT_FOREGROUND
@@ -535,8 +541,11 @@
mAvailable = false;
mStoppedReason = null;
mStoppedTime = 0;
+ // Wait until after bindService() returns a success value to set these so we don't
+ // have JobStatus objects that aren't running but have these set to true.
job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
job.startedAsUserInitiatedJob = job.shouldTreatAsUserInitiatedJob();
+ job.startedWithForegroundFlag = startedWithForegroundFlag;
return true;
}
}
@@ -1331,7 +1340,7 @@
// FINISHED/NO-RETRY.
onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
/* texCounterMetricId */
- "job_scheduler.value_cntr_w_uid_slow_app_response_onStartJob",
+ "job_scheduler.value_cntr_w_uid_slow_app_response_on_start_job",
/* debugReason */ "timed out while starting",
/* anrMessage */ "No response to onStartJob",
CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1343,7 +1352,7 @@
// other reason.
onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ false,
/* texCounterMetricId */
- "job_scheduler.value_cntr_w_uid_slow_app_response_onStopJob",
+ "job_scheduler.value_cntr_w_uid_slow_app_response_on_stop_job",
/* debugReason */ "timed out while stopping",
/* anrMessage */ "No response to onStopJob",
CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1400,7 +1409,7 @@
} else if (mAwaitingNotification) {
onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ true,
/* texCounterMetricId */
- "job_scheduler.value_cntr_w_uid_slow_app_response_setNotification",
+ "job_scheduler.value_cntr_w_uid_slow_app_response_set_notification",
/* debugReason */ "timed out while stopping",
/* anrMessage */ "required notification not provided",
/* triggerAnr */ true);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 0a7bffc..ebc638e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1184,6 +1184,12 @@
}
}
+ /** Returns the {@link String#intern() interned} String if it's not null. */
+ @Nullable
+ private static String intern(@Nullable String val) {
+ return val == null ? null : val.intern();
+ }
+
private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood, long nowElapsed)
throws XmlPullParserException, IOException {
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
@@ -1478,8 +1484,8 @@
throws XmlPullParserException {
// Pull out required fields from <job> attributes.
int jobId = parser.getAttributeInt(null, "jobid");
- String packageName = parser.getAttributeValue(null, "package");
- String className = parser.getAttributeValue(null, "class");
+ String packageName = intern(parser.getAttributeValue(null, "package"));
+ String className = intern(parser.getAttributeValue(null, "class"));
ComponentName cname = new ComponentName(packageName, className);
return new JobInfo.Builder(jobId, cname);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index ecee10a..25b3421 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -19,6 +19,7 @@
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -205,8 +206,32 @@
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
- jobStatus.canRunInBatterySaver());
+ final boolean isUserBgRestricted =
+ !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName);
+ // If a job started with the foreground flag, it'll cause the UID to stay active
+ // and thus cause areJobsRestricted() to always return false, so if
+ // areJobsRestricted() returns false and the app is BG restricted and not TOP,
+ // we need to stop any jobs that started with the foreground flag so they don't
+ // keep the app in an elevated proc state. If we were to get in this situation,
+ // then the user restricted the app after the job started, so it's best to stop
+ // the job as soon as possible, especially since the job would be visible to the
+ // user (with a notification and in Task Manager).
+ // There are several other reasons that uidActive can be true for an app even if its
+ // proc state is less important than BFGS.
+ // JobScheduler has historically (at least up through UDC) allowed the app's jobs to run
+ // when its UID was active, even if it's background restricted. This has been fine because
+ // JobScheduler stops the job as soon as the UID becomes inactive and the jobs themselves
+ // will not keep the UID active. The logic here is to ensure that special jobs
+ // (e.g. user-initiated jobs) themselves do not keep the UID active when the app is
+ // background restricted.
+ final boolean shouldStopImmediately = jobStatus.startedWithForegroundFlag
+ && isUserBgRestricted
+ && mService.getUidProcState(uid)
+ > ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ final boolean canRun = !shouldStopImmediately
+ && !mAppStateTracker.areJobsRestricted(
+ uid, packageName, jobStatus.canRunInBatterySaver());
final boolean isActive;
if (activeState == UNKNOWN) {
@@ -219,8 +244,7 @@
}
boolean didChange =
jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
- !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
- && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
+ isUserBgRestricted);
didChange |= jobStatus.setUidActive(isActive);
return didChange;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index f6bdb93..6d938de 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -1774,6 +1774,12 @@
}
pw.println();
+ if (mBackgroundMeteredAllowed.size() > 0) {
+ pw.print("Background metered allowed: ");
+ pw.println(mBackgroundMeteredAllowed);
+ pw.println();
+ }
+
pw.println("Current default network callbacks:");
pw.increaseIndent();
for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
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 edd531d..13903ac 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
@@ -430,6 +430,13 @@
* when it started running. This isn't copied over when a job is rescheduled.
*/
public boolean startedAsUserInitiatedJob = false;
+ /**
+ * Whether this particular JobStatus instance started with the foreground flag
+ * (or more accurately, did <b>not</b> have the
+ * {@link android.content.Context#BIND_NOT_FOREGROUND} flag
+ * included in its binding flags when started).
+ */
+ public boolean startedWithForegroundFlag = false;
public boolean startedWithImmediacyPrivilege = false;
@@ -1088,13 +1095,77 @@
return UserHandle.getUserId(callingUid);
}
+ private boolean shouldBlameSourceForTimeout() {
+ // If the system scheduled the job on behalf of an app, assume the app is the one
+ // doing the work and blame the app directly. This is the case with things like
+ // syncs via SyncManager.
+ // If the system didn't schedule the job on behalf of an app, then
+ // blame the app doing the actual work. Proxied jobs are a little tricky.
+ // Proxied jobs scheduled by built-in system apps like DownloadManager may be fine
+ // and we could consider exempting those jobs. For example, in DownloadManager's
+ // case, all it does is download files and the code is vetted. A timeout likely
+ // means it's downloading a large file, which isn't an error. For now, DownloadManager
+ // is an exempted app, so this shouldn't be an issue.
+ // However, proxied jobs coming from other system apps (such as those that can
+ // be updated separately from an OTA) may not be fine and we would want to apply
+ // this policy to those jobs/apps.
+ // TODO(284512488): consider exempting DownloadManager or other system apps
+ return UserHandle.isCore(callingUid);
+ }
+
+ /**
+ * Returns the package name that should most likely be blamed for the job timing out.
+ */
+ public String getTimeoutBlamePackageName() {
+ if (shouldBlameSourceForTimeout()) {
+ return sourcePackageName;
+ }
+ return getServiceComponent().getPackageName();
+ }
+
+ /**
+ * Returns the UID that should most likely be blamed for the job timing out.
+ */
+ public int getTimeoutBlameUid() {
+ if (shouldBlameSourceForTimeout()) {
+ return sourceUid;
+ }
+ return callingUid;
+ }
+
+ /**
+ * Returns the userId that should most likely be blamed for the job timing out.
+ */
+ public int getTimeoutBlameUserId() {
+ if (shouldBlameSourceForTimeout()) {
+ return sourceUserId;
+ }
+ return UserHandle.getUserId(callingUid);
+ }
+
/**
* Returns an appropriate standby bucket for the job, taking into account any standby
* exemptions.
*/
public int getEffectiveStandbyBucket() {
+ final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
+ final boolean isBuggy = jsi.isAppConsideredBuggy(
+ getUserId(), getServiceComponent().getPackageName(),
+ getTimeoutBlameUserId(), getTimeoutBlamePackageName());
+
final int actualBucket = getStandbyBucket();
if (actualBucket == EXEMPTED_INDEX) {
+ // EXEMPTED apps always have their jobs exempted, even if they're buggy, because the
+ // user has explicitly told the system to avoid restricting the app for power reasons.
+ if (isBuggy) {
+ final String pkg;
+ if (getServiceComponent().getPackageName().equals(sourcePackageName)) {
+ pkg = sourcePackageName;
+ } else {
+ pkg = getServiceComponent().getPackageName() + "/" + sourcePackageName;
+ }
+ Slog.w(TAG, "Exempted app " + pkg + " considered buggy");
+ }
return actualBucket;
}
if (uidActive || getJob().isExemptedFromAppStandby()) {
@@ -1102,13 +1173,18 @@
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
+ // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
+ // (potentially because it's used frequently by the user), limit its effective bucket
+ // so that it doesn't get to run as much as a normal ACTIVE app.
+ final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
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
+ // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
+ // to the user, and the
// source package may not have been used directly in a while.
- return Math.min(WORKING_INDEX, actualBucket);
+ return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
}
- return actualBucket;
+ return Math.max(highestBucket, actualBucket);
}
/** Returns the real standby bucket of the job. */
@@ -1537,6 +1613,10 @@
* for any reason.
*/
public boolean shouldTreatAsUserInitiatedJob() {
+ // isUserBgRestricted is intentionally excluded from this method. It should be fine to
+ // treat the job as a UI job while the app is TOP, but just not in the background.
+ // Instead of adding a proc state check here, the parts of JS that can make the distinction
+ // and care about the distinction can do the check.
return getJob().isUserInitiated()
&& (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0
&& (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ) == 0;
@@ -1584,6 +1664,11 @@
&& (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
}
+ /** Returns whether or not the app is background restricted by the user (FAS). */
+ public boolean isUserBgRestricted() {
+ return mIsUserBgRestricted;
+ }
+
/** @return true if the constraint was changed, false otherwise. */
boolean setChargingConstraintSatisfied(final long nowElapsed, boolean state) {
return setConstraintSatisfied(CONSTRAINT_CHARGING, nowElapsed, state);
@@ -2733,6 +2818,12 @@
}
pw.decreaseIndent();
+ pw.print("Started with foreground flag: ");
+ pw.println(startedWithForegroundFlag);
+ if (mIsUserBgRestricted) {
+ pw.println("User BG restricted");
+ }
+
if (changedAuthorities != null) {
pw.println("Changed authorities:");
pw.increaseIndent();
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 07958dd..1c29982 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
@@ -773,6 +773,14 @@
// If quota is currently "free", then the job can run for the full amount of time,
// regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
if (mService.isBatteryCharging()
+ // The top and foreground cases here were added because apps in those states
+ // aren't really restricted and the work could be something the user is
+ // waiting for. Now that user-initiated jobs are a defined concept, we may
+ // not need these exemptions as much. However, UIJs are currently limited
+ // (as of UDC) to data transfer work. There may be other work that could
+ // rely on this exception. Once we add more UIJ types, we can re-evaluate
+ // the need for these exceptions.
+ // TODO: re-evaluate the need for these exceptions
|| mTopAppCache.get(jobStatus.getSourceUid())
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 08439f3..87e1249 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -433,6 +433,12 @@
}
}
+ /** Returns the {@link String#intern() interned} String if it's not null. */
+ @Nullable
+ private static String intern(@Nullable String val) {
+ return val == null ? null : val.intern();
+ }
+
/**
* @param parser Xml parser at the beginning of a "<ledger/>" tag. The next "parser.next()" call
* will take the parser into the body of the ledger tag.
@@ -448,7 +454,7 @@
final List<Ledger.Transaction> transactions = new ArrayList<>();
final List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
- pkgName = parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME);
+ pkgName = intern(parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME));
curBalance = parser.getAttributeLong(null, XML_ATTR_CURRENT_BALANCE);
final boolean isInstalled = validPackages.contains(pkgName);
@@ -487,7 +493,7 @@
}
continue;
}
- final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
+ final String tag = intern(parser.getAttributeValue(null, XML_ATTR_TAG));
final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
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 55e6815..7d38377 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -3025,7 +3025,7 @@
public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 30 * ONE_MINUTE;
public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
- COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
+ COMPRESS_TIME ? ONE_MINUTE : ONE_HOUR;
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;
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35c8a58..7a1d4c2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3415,7 +3415,9 @@
}
public final class Choreographer {
+ method public long getExpectedPresentationTimeNanos();
method public static long getFrameDelay();
+ method public long getFrameTimeNanos();
method public void postCallback(int, Runnable, Object);
method public void postCallbackDelayed(int, Runnable, Object, long);
method public void removeCallbacks(int, Runnable, Object);
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 2198fcd..f67c68e 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -111,20 +111,20 @@
float pathErrorScale) throws NotFoundException {
final ConfigurationBoundResourceCache<Animator> animatorCache = resources
.getAnimatorCache();
- ConfigurationBoundResourceCache.Entry<Animator> animatorEntry =
- animatorCache.getInstance(id, resources, theme);
- if (animatorEntry.hasValue()) {
+ Animator animator = animatorCache.getInstance(id, resources, theme);
+ if (animator != null) {
if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
}
- return animatorEntry.getValue();
+ return animator;
} else if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
}
+ int cacheGeneration = animatorCache.getGeneration();
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- Animator animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
if (animator != null) {
animator.appendChangingConfigurations(getChangingConfigs(resources, id));
final ConstantState<Animator> constantState = animator.createConstantState();
@@ -132,7 +132,7 @@
if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
}
- animatorCache.put(id, theme, constantState, animatorEntry.getGeneration());
+ animatorCache.put(id, theme, constantState, cacheGeneration);
// create a new animator so that cached version is never used by the user
animator = constantState.newInstance(resources, theme);
}
@@ -161,22 +161,22 @@
final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
.getStateListAnimatorCache();
final Theme theme = context.getTheme();
- ConfigurationBoundResourceCache.Entry<StateListAnimator> animatorEntry =
- cache.getInstance(id, resources, theme);
- if (animatorEntry.hasValue()) {
- return animatorEntry.getValue();
+ StateListAnimator animator = cache.getInstance(id, resources, theme);
+ if (animator != null) {
+ return animator;
}
+ int cacheGeneration = cache.getGeneration();
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- StateListAnimator animator =
+ animator =
createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
if (animator != null) {
animator.appendChangingConfigurations(getChangingConfigs(resources, id));
final ConstantState<StateListAnimator> constantState = animator
.createConstantState();
if (constantState != null) {
- cache.put(id, theme, constantState, animatorEntry.getGeneration());
+ cache.put(id, theme, constantState, cacheGeneration);
// return a clone so that the animator in constant state is never used.
animator = constantState.newInstance(resources, theme);
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 82f4315..2056eca 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3478,6 +3478,16 @@
((CompatResources) r).setContext(this);
}
mResources = r;
+
+ // only do this if the user already has more than one preferred locale
+ if (r.getConfiguration().getLocales().size() > 1) {
+ LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
+ mResourcesManager.setLocaleList(lc != null
+ && lc.getSupportedLocales() != null
+ && !lc.getSupportedLocales().isEmpty()
+ ? lc.getSupportedLocales()
+ : null);
+ }
}
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0ec3847..46260ea 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -607,6 +607,7 @@
void killPackageDependents(in String packageName, int userId);
void makePackageIdle(String packageName, int userId);
+ void setDeterministicUidIdle(boolean deterministic);
int getMemoryTrimLevel();
boolean isVrModePackageEnabled(in ComponentName packageName);
void notifyLockedProfile(int userId);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 09450f5..e7a2b61e 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -117,6 +117,26 @@
"android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL";
/**
+ * Intent used to prompt user for device credential for entering repair
+ * mode. If the credential is verified successfully, then the information
+ * needed to verify the credential again will be written to a location that
+ * is available to repair mode. This makes it possible for repair mode to
+ * require that the same credential be provided to exit repair mode.
+ * @hide
+ */
+ public static final String ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL =
+ "android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+ /**
+ * Intent used to prompt user for device credential that is written by
+ * {@link #ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL} for exiting
+ * repair mode.
+ * @hide
+ */
+ public static final String ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL =
+ "android.app.action.CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+ /**
* A CharSequence dialog title to show to the user when used with a
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
* @hide
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 5031a33..729e555 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -138,6 +138,10 @@
try {
//Get the resource id
resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
+ if (resId == 0) {
+ mStatus = STATUS_NOT_SPECIFIED;
+ return;
+ }
//Get the parser to read XML data
XmlResourceParser parser = res.getXml(resId);
parseLocaleConfig(parser, res);
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7e6386e..1ecb5d3 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -36,6 +36,7 @@
import android.content.res.loader.ResourcesLoader;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.Process;
import android.os.Trace;
import android.util.ArrayMap;
@@ -118,6 +119,11 @@
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
+ /**
+ * The list of locales the app declares it supports.
+ */
+ private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+
private static class ApkKey {
public final String path;
public final boolean sharedLib;
@@ -1605,6 +1611,22 @@
}
}
+ /**
+ * Returns the LocaleList current set
+ */
+ public LocaleList getLocaleList() {
+ return mLocaleList;
+ }
+
+ /**
+ * Sets the LocaleList of app's supported locales
+ */
+ public void setLocaleList(LocaleList localeList) {
+ if ((localeList != null) && !localeList.isEmpty()) {
+ mLocaleList = localeList;
+ }
+ }
+
private class UpdateHandler implements Resources.UpdateCallbacks {
/**
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 4b8cfd5..c4e4995 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -334,6 +334,12 @@
public boolean isVisible;
/**
+ * Whether this task is request visible.
+ * @hide
+ */
+ public boolean isVisibleRequested;
+
+ /**
* Whether this task is sleeping due to sleeping display.
* @hide
*/
@@ -518,6 +524,7 @@
&& Objects.equals(taskDescription, that.taskDescription)
&& isFocused == that.isFocused
&& isVisible == that.isVisible
+ && isVisibleRequested == that.isVisibleRequested
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
&& parentTaskId == that.parentTaskId
@@ -591,6 +598,7 @@
parentTaskId = source.readInt();
isFocused = source.readBoolean();
isVisible = source.readBoolean();
+ isVisibleRequested = source.readBoolean();
isSleeping = source.readBoolean();
topActivityInSizeCompat = source.readBoolean();
topActivityEligibleForLetterboxEducation = source.readBoolean();
@@ -644,6 +652,7 @@
dest.writeInt(parentTaskId);
dest.writeBoolean(isFocused);
dest.writeBoolean(isVisible);
+ dest.writeBoolean(isVisibleRequested);
dest.writeBoolean(isSleeping);
dest.writeBoolean(topActivityInSizeCompat);
dest.writeBoolean(topActivityEligibleForLetterboxEducation);
@@ -687,6 +696,7 @@
+ " parentTaskId=" + parentTaskId
+ " isFocused=" + isFocused
+ " isVisible=" + isVisible
+ + " isVisibleRequested=" + isVisibleRequested
+ " isSleeping=" + isSleeping
+ " topActivityInSizeCompat=" + topActivityInSizeCompat
+ " topActivityEligibleForLetterboxEducation= "
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b2a9230..da5e40a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11369,7 +11369,8 @@
* @throws SecurityException if the caller is not a profile owner on an organization-owned
* managed profile.
* @throws IllegalStateException if called after the device setup has been completed.
- * @throws UnsupportedOperationException if the api is not enabled.
+ * @throws UnsupportedOperationException if managed subscriptions policy is not explicitly
+ * enabled by the device policy management role holder during device setup.
* @see ManagedSubscriptionsPolicy
*/
public void setManagedSubscriptionsPolicy(@Nullable ManagedSubscriptionsPolicy policy) {
diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java
index 4da3c18..5e10a57 100644
--- a/core/java/android/content/res/ConfigurationBoundResourceCache.java
+++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java
@@ -37,16 +37,16 @@
* @param key a key that uniquely identifies the drawable resource
* @param resources a Resources object from which to create new instances.
* @param theme the theme where the resource will be used
- * @return an Entry wrapping a new instance of the resource, or {@code null} if not in
+ * @return a new instance of the resource, or {@code null} if not in
* the cache
*/
- public Entry<T> getInstance(long key, Resources resources, Resources.Theme theme) {
- final Entry<ConstantState<T>> e = get(key, theme);
- if (e.hasValue()) {
- return new Entry<>(e.getValue().newInstance(resources, theme), e.getGeneration());
+ public T getInstance(long key, Resources resources, Resources.Theme theme) {
+ final ConstantState<T> entry = get(key, theme);
+ if (entry != null) {
+ return entry.newInstance(resources, theme);
}
- return new Entry<>(null, e.getGeneration());
+ return null;
}
@Override
diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java
index b51d14a..d0ebe33 100644
--- a/core/java/android/content/res/DrawableCache.java
+++ b/core/java/android/content/res/DrawableCache.java
@@ -40,32 +40,14 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
- final Entry<Drawable.ConstantState> entry = get(key, theme);
- if (entry.getValue() != null) {
- return entry.getValue().newDrawable(resources, theme);
+ final Drawable.ConstantState entry = get(key, theme);
+ if (entry != null) {
+ return entry.newDrawable(resources, theme);
}
return null;
}
- /**
- * If the resource is cached, creates and returns a new instance of it.
- *
- * @param key a key that uniquely identifies the drawable resource
- * @param resources a Resources object from which to create new instances.
- * @param theme the theme where the resource will be used
- * @return an Entry wrapping a a new instance of the resource, or {@code null} if not in
- * the cache
- */
- public Entry<Drawable> getDrawable(long key, Resources resources, Resources.Theme theme) {
- final Entry<Drawable.ConstantState> e = get(key, theme);
- if (e.hasValue()) {
- return new Entry<>(e.getValue().newDrawable(resources, theme), e.getGeneration());
- }
-
- return new Entry<>(null, e.getGeneration());
- }
-
@Override
public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) {
return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 25154d5..61d5aa6 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,7 @@
import android.annotation.RawRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -408,17 +409,24 @@
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
- // The LocaleList has changed. We must query the AssetManager's available
- // Locales and figure out the best matching Locale in the new LocaleList.
- String[] availableLocales = mAssets.getNonSystemLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- // No app defined locales, so grab the system locales.
- availableLocales = mAssets.getLocales();
+ String[] availableLocales;
+
+ LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
+ if (!localeList.isEmpty()) {
+ availableLocales = localeList.toLanguageTags().split(",");
+ } else {
+ // The LocaleList has changed. We must query the AssetManager's
+ // available Locales and figure out the best matching Locale in the new
+ // LocaleList.
+ availableLocales = mAssets.getNonSystemLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- availableLocales = null;
+ // No app defined locales, so grab the system locales.
+ availableLocales = mAssets.getLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ availableLocales = null;
+ }
}
}
-
if (availableLocales != null) {
final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
availableLocales);
@@ -650,21 +658,16 @@
key = (((long) value.assetCookie) << 32) | value.data;
}
- int cacheGeneration;
+ int cacheGeneration = caches.getGeneration();
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme. Skip the cache if
// we're currently preloading or we're not using the cache.
if (!mPreloading && useCache) {
- final ThemedResourceCache.Entry<Drawable> cachedDrawable =
- caches.getDrawable(key, wrapper, theme);
- if (cachedDrawable.hasValue()) {
- cachedDrawable.getValue().setChangingConfigurations(
- value.changingConfigurations);
- return cachedDrawable.getValue();
+ Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
+ if (cachedDrawable != null) {
+ cachedDrawable.setChangingConfigurations(value.changingConfigurations);
+ return cachedDrawable;
}
- cacheGeneration = cachedDrawable.getGeneration();
- } else {
- cacheGeneration = ThemedResourceCache.UNDEFINED_GENERATION;
}
// Next, check preloaded drawables. Preloaded drawables may contain
@@ -1009,16 +1012,15 @@
TypedValue value, int id) {
final long key = (((long) value.assetCookie) << 32) | value.data;
final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
- ThemedResourceCache.Entry<ComplexColor> complexColorEntry =
- cache.getInstance(key, wrapper, theme);
- if (complexColorEntry.hasValue()) {
- return complexColorEntry.getValue();
+ ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
+ if (complexColor != null) {
+ return complexColor;
}
+ int cacheGeneration = cache.getGeneration();
final android.content.res.ConstantState<ComplexColor> factory =
sPreloadedComplexColors.get(key);
- ComplexColor complexColor = null;
if (factory != null) {
complexColor = factory.newInstance(wrapper, theme);
}
@@ -1035,8 +1037,7 @@
sPreloadedComplexColors.put(key, complexColor.getConstantState());
}
} else {
- cache.put(key, theme, complexColor.getConstantState(),
- complexColorEntry.getGeneration());
+ cache.put(key, theme, complexColor.getConstantState(), cacheGeneration);
}
}
return complexColor;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index e7fd275..a7cd168 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -41,29 +41,6 @@
private int mGeneration;
- public static class Entry<S> {
- private S mValue;
- private int mGeneration;
-
-
- public S getValue() {
- return mValue;
- }
-
- public boolean hasValue() {
- return mValue != null;
- }
-
- public int getGeneration() {
- return mGeneration;
- }
-
- Entry(S value, int generation) {
- this.mValue = value;
- this.mGeneration = generation;
- }
- }
-
/**
* Adds a new theme-dependent entry to the cache.
*
@@ -109,6 +86,15 @@
}
/**
+ * Returns the current generation of the cache
+ *
+ * @return The current generation
+ */
+ public int getGeneration() {
+ return mGeneration;
+ }
+
+ /**
* Returns an entry from the cache.
*
* @param key a key that uniquely identifies the entry
@@ -116,7 +102,7 @@
* @return a cached entry, or {@code null} if not in the cache
*/
@Nullable
- public Entry get(long key, @Nullable Theme theme) {
+ public T get(long key, @Nullable Theme theme) {
// The themed (includes null-themed) and unthemed caches are mutually
// exclusive, so we'll give priority to whichever one we think we'll
// hit first. Since most of the framework drawables are themed, that's
@@ -126,7 +112,7 @@
if (themedEntries != null) {
final WeakReference<T> themedEntry = themedEntries.get(key);
if (themedEntry != null) {
- return new Entry(themedEntry.get(), mGeneration);
+ return themedEntry.get();
}
}
@@ -134,14 +120,15 @@
if (unthemedEntries != null) {
final WeakReference<T> unthemedEntry = unthemedEntries.get(key);
if (unthemedEntry != null) {
- return new Entry(unthemedEntry.get(), mGeneration);
+ return unthemedEntry.get();
}
}
}
- return new Entry(null, mGeneration);
+ return null;
}
+
/**
* Prunes cache entries that have been invalidated by a configuration
* change.
diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl
deleted file mode 100644
index da0bf4c..0000000
--- a/core/java/android/net/ITetheringStatsProvider.aidl
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 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.net.NetworkStats;
-
-/**
- * Interface for NetworkManagementService to query tethering statistics and set data limits.
- *
- * TODO: this does not really need to be an interface since Tethering runs in the same process
- * as NetworkManagementService. Consider refactoring Tethering to use direct access to
- * NetworkManagementService instead of using INetworkManagementService, and then deleting this
- * interface.
- *
- * @hide
- */
-interface ITetheringStatsProvider {
- // Returns cumulative statistics for all tethering sessions since boot, on all upstreams.
- // @code {how} is one of the NetworkStats.STATS_PER_* constants. If {@code how} is
- // {@code STATS_PER_IFACE}, the provider should not include any traffic that is already
- // counted by kernel interface counters.
- NetworkStats getTetherStats(int how);
-
- // Sets the interface quota for the specified upstream interface. This is defined as the number
- // of bytes, starting from zero and counting from now, after which data should stop being
- // forwarded to/from the specified upstream. A value of QUOTA_UNLIMITED means there is no limit.
- void setInterfaceQuota(String iface, long quotaBytes);
-
- // Indicates that no data usage limit is set.
- const int QUOTA_UNLIMITED = -1;
-}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index ba4f82d..70de477 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -882,10 +882,11 @@
}
static Uri readFrom(Parcel parcel) {
+ final StringUri stringUri = new StringUri(parcel.readString8());
return new OpaqueUri(
- parcel.readString8(),
- Part.readFrom(parcel),
- Part.readFrom(parcel)
+ stringUri.parseScheme(),
+ stringUri.getSsp(),
+ stringUri.getFragmentPart()
);
}
@@ -895,9 +896,7 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(TYPE_ID);
- parcel.writeString8(scheme);
- ssp.writeTo(parcel);
- fragment.writeTo(parcel);
+ parcel.writeString8(toString());
}
public boolean isHierarchical() {
@@ -1196,22 +1195,25 @@
Part query, Part fragment) {
this.scheme = scheme;
this.authority = Part.nonNull(authority);
- this.path = path == null ? PathPart.NULL : path;
+ this.path = generatePath(path);
this.query = Part.nonNull(query);
this.fragment = Part.nonNull(fragment);
}
- static Uri readFrom(Parcel parcel) {
- final String scheme = parcel.readString8();
- final Part authority = Part.readFrom(parcel);
+ private PathPart generatePath(PathPart originalPath) {
// In RFC3986 the path should be determined based on whether there is a scheme or
// authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3).
final boolean hasSchemeOrAuthority =
(scheme != null && scheme.length() > 0) || !authority.isEmpty();
- final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel);
- final Part query = Part.readFrom(parcel);
- final Part fragment = Part.readFrom(parcel);
- return new HierarchicalUri(scheme, authority, path, query, fragment);
+ final PathPart newPath = hasSchemeOrAuthority ? PathPart.makeAbsolute(originalPath)
+ : originalPath;
+ return newPath == null ? PathPart.NULL : newPath;
+ }
+
+ static Uri readFrom(Parcel parcel) {
+ final StringUri stringUri = new StringUri(parcel.readString8());
+ return new HierarchicalUri(stringUri.getScheme(), stringUri.getAuthorityPart(),
+ stringUri.getPathPart(), stringUri.getQueryPart(), stringUri.getFragmentPart());
}
public int describeContents() {
@@ -1220,11 +1222,7 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(TYPE_ID);
- parcel.writeString8(scheme);
- authority.writeTo(parcel);
- path.writeTo(parcel);
- query.writeTo(parcel);
- fragment.writeTo(parcel);
+ parcel.writeString8(toString());
}
public boolean isHierarchical() {
@@ -1994,21 +1992,14 @@
*/
static abstract class AbstractPart {
- // Possible values of mCanonicalRepresentation.
- static final int REPRESENTATION_ENCODED = 1;
- static final int REPRESENTATION_DECODED = 2;
-
volatile String encoded;
volatile String decoded;
- private final int mCanonicalRepresentation;
AbstractPart(String encoded, String decoded) {
if (encoded != NotCachedHolder.NOT_CACHED) {
- this.mCanonicalRepresentation = REPRESENTATION_ENCODED;
this.encoded = encoded;
this.decoded = NotCachedHolder.NOT_CACHED;
} else if (decoded != NotCachedHolder.NOT_CACHED) {
- this.mCanonicalRepresentation = REPRESENTATION_DECODED;
this.encoded = NotCachedHolder.NOT_CACHED;
this.decoded = decoded;
} else {
@@ -2023,24 +2014,6 @@
boolean hasDecoded = decoded != NotCachedHolder.NOT_CACHED;
return hasDecoded ? decoded : (decoded = decode(encoded));
}
-
- final void writeTo(Parcel parcel) {
- final String canonicalValue;
- if (mCanonicalRepresentation == REPRESENTATION_ENCODED) {
- canonicalValue = encoded;
- } else if (mCanonicalRepresentation == REPRESENTATION_DECODED) {
- canonicalValue = decoded;
- } else {
- throw new IllegalArgumentException("Unknown representation: "
- + mCanonicalRepresentation);
- }
- if (canonicalValue == NotCachedHolder.NOT_CACHED) {
- throw new AssertionError("Canonical value not cached ("
- + mCanonicalRepresentation + ")");
- }
- parcel.writeInt(mCanonicalRepresentation);
- parcel.writeString8(canonicalValue);
- }
}
/**
@@ -2069,20 +2042,6 @@
return hasEncoded ? encoded : (encoded = encode(decoded));
}
- static Part readFrom(Parcel parcel) {
- int representation = parcel.readInt();
- String value = parcel.readString8();
- switch (representation) {
- case REPRESENTATION_ENCODED:
- return fromEncoded(value);
- case REPRESENTATION_DECODED:
- return fromDecoded(value);
- default:
- throw new IllegalArgumentException("Unknown representation: "
- + representation);
- }
- }
-
/**
* Returns given part or {@link #NULL} if the given part is null.
*/
@@ -2258,23 +2217,6 @@
return appendEncodedSegment(oldPart, encoded);
}
- static PathPart readFrom(Parcel parcel) {
- int representation = parcel.readInt();
- switch (representation) {
- case REPRESENTATION_ENCODED:
- return fromEncoded(parcel.readString8());
- case REPRESENTATION_DECODED:
- return fromDecoded(parcel.readString8());
- default:
- throw new IllegalArgumentException("Unknown representation: " + representation);
- }
- }
-
- static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) {
- final PathPart path = readFrom(parcel);
- return hasSchemeOrAuthority ? makeAbsolute(path) : path;
- }
-
/**
* Creates a path from the encoded string.
*
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 40f7533..ed14652 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -19,7 +19,6 @@
import android.net.InterfaceConfiguration;
import android.net.INetworkManagementEventObserver;
-import android.net.ITetheringStatsProvider;
import android.net.Network;
import android.net.NetworkStats;
import android.net.RouteInfo;
@@ -144,14 +143,6 @@
void startTethering(in String[] dhcpRanges);
/**
- * Start tethering services with the specified dhcp server range and
- * DNS proxy config.
- * {@code boolean} is used to control legacy DNS proxy server.
- * {@code String[]} is a set of start end pairs defining the ranges.
- */
- void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, in String[] dhcpRanges);
-
- /**
* Stop currently running tethering services
*/
@UnsupportedAppUsage
@@ -182,23 +173,6 @@
String[] listTetheredInterfaces();
/**
- * Returns the list of DNS forwarders (in order of priority)
- */
- String[] getDnsForwarders();
-
- /**
- * Enables unidirectional packet forwarding from {@code fromIface} to
- * {@code toIface}.
- */
- void startInterfaceForwarding(String fromIface, String toIface);
-
- /**
- * Disables unidirectional packet forwarding from {@code fromIface} to
- * {@code toIface}.
- */
- void stopInterfaceForwarding(String fromIface, String toIface);
-
- /**
* Enables Network Address Translation between two interfaces.
* The address and netmask of the external interface is used for
* the NAT'ed network.
@@ -213,42 +187,10 @@
void disableNat(String internalInterface, String externalInterface);
/**
- * Registers a {@code ITetheringStatsProvider} to provide tethering statistics.
- * All registered providers will be called in order, and their results will be added together.
- * Netd is always registered as a tethering stats provider.
- */
- void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name);
-
- /**
- * Unregisters a previously-registered {@code ITetheringStatsProvider}.
- */
- void unregisterTetheringStatsProvider(ITetheringStatsProvider provider);
-
- /**
- * Reports that a tethering provider has reached a data limit.
- *
- * Currently triggers a global alert, which causes NetworkStatsService to poll counters and
- * re-evaluate data usage.
- *
- * This does not take an interface name because:
- * 1. The tethering offload stats provider cannot reliably determine the interface on which the
- * limit was reached, because the HAL does not provide it.
- * 2. Firing an interface-specific alert instead of a global alert isn't really useful since in
- * all cases of interest, the system responds to both in the same way - it polls stats, and
- * then notifies NetworkPolicyManagerService of the fact.
- */
- void tetherLimitReached(ITetheringStatsProvider provider);
-
- /**
** DATA USAGE RELATED
**/
/**
- * Return summary of network statistics all tethering interfaces.
- */
- NetworkStats getNetworkStatsTethering(int how);
-
- /**
* Set quota for an interface.
*/
void setInterfaceQuota(String iface, long quotaBytes);
@@ -269,11 +211,6 @@
void removeInterfaceAlert(String iface);
/**
- * Set alert across all interfaces.
- */
- void setGlobalAlert(long alertBytes);
-
- /**
* Control network activity of a UID over interfaces with a quota limit.
*/
void setUidOnMeteredNetworkDenylist(int uid, boolean enable);
@@ -291,7 +228,6 @@
void setFirewallEnabled(boolean enabled);
boolean isFirewallEnabled();
- void setFirewallInterfaceRule(String iface, boolean allow);
void setFirewallUidRule(int chain, int uid, int rule);
void setFirewallUidRules(int chain, in int[] uids, in int[] rules);
void setFirewallChainEnabled(int chain, boolean enable);
@@ -306,10 +242,6 @@
*/
void denyProtect(int uid);
- void addInterfaceToLocalNetwork(String iface, in List<RouteInfo> routes);
- void removeInterfaceFromLocalNetwork(String iface);
- int removeRoutesFromLocalNetwork(in List<RouteInfo> routes);
-
@EnforcePermission("OBSERVE_NETWORK_POLICY")
boolean isNetworkRestricted(int uid);
}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index ef39010..0527ed6 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -606,12 +606,9 @@
@Override
public boolean equals(@Nullable Object obj) {
- try {
- if (obj != null) {
- UserHandle other = (UserHandle)obj;
- return mHandle == other.mHandle;
- }
- } catch (ClassCastException ignore) {
+ if (obj instanceof UserHandle) {
+ UserHandle other = (UserHandle) obj;
+ return mHandle == other.mHandle;
}
return false;
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index b0dda6f..e256ea0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -177,6 +177,13 @@
public static final long CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS = 146211400;
/**
+ * Whether to use the new {@link com.android.server.permission.access.AccessCheckingService}.
+ *
+ * @hide
+ */
+ public static final boolean USE_ACCESS_CHECKING_SERVICE = false;
+
+ /**
* The time to wait in between refreshing the exempted indicator role packages
*/
private static final long EXEMPTED_INDICATOR_ROLE_UPDATE_FREQUENCY_MS = 15000;
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 4a848dd..8cf2ce4 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -123,6 +123,12 @@
*/
public static final @RequestFlags int FLAG_PCC_DETECTION = 0x200;
+ /**
+ * Indicate whether the screen has credman field
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD = 0x400;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -236,7 +242,8 @@
FLAG_SUPPORTS_FILL_DIALOG,
FLAG_IME_SHOWING,
FLAG_RESET_FILL_DIALOG_STATE,
- FLAG_PCC_DETECTION
+ FLAG_PCC_DETECTION,
+ FLAG_SCREEN_HAS_CREDMAN_FIELD
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -268,6 +275,8 @@
return "FLAG_RESET_FILL_DIALOG_STATE";
case FLAG_PCC_DETECTION:
return "FLAG_PCC_DETECTION";
+ case FLAG_SCREEN_HAS_CREDMAN_FIELD:
+ return "FLAG_SCREEN_HAS_CREDMAN_FIELD";
default: return Integer.toHexString(value);
}
}
@@ -284,10 +293,15 @@
* include contexts from requests whose {@link SaveInfo} had the
* {@link SaveInfo#FLAG_DELAY_SAVE} flag.
* @param hints
- * Autofill Provider should return data for the autofill hints requested here,
+ * Sends a list of datatypes for the Autofill Provider.
+ *
+ * If this is populated, Autofill Provider should return data
+ * for the autofill hints requested here,
* even though the Autofill Provider may not have detected these types.
* The hints would be part of HintConstants:
* https://developer.android.com/reference/androidx/autofill/HintConstants
+ *
+ * This is populated if the platform's field detection is enabled.
* @param clientState
* Gets the latest client state bundle set by the service in a
* {@link FillResponse.Builder#setClientState(Bundle) fill response}.
@@ -355,7 +369,8 @@
| FLAG_SUPPORTS_FILL_DIALOG
| FLAG_IME_SHOWING
| FLAG_RESET_FILL_DIALOG_STATE
- | FLAG_PCC_DETECTION);
+ | FLAG_PCC_DETECTION
+ | FLAG_SCREEN_HAS_CREDMAN_FIELD);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -383,10 +398,15 @@
}
/**
- * Autofill Provider should return data for the autofill hints requested here,
+ * Sends a list of datatypes for the Autofill Provider.
+ *
+ * If this is populated, Autofill Provider should return data
+ * for the autofill hints requested here,
* even though the Autofill Provider may not have detected these types.
* The hints would be part of HintConstants:
* https://developer.android.com/reference/androidx/autofill/HintConstants
+ *
+ * This is populated if the platform's field detection is enabled.
*/
@DataClass.Generated.Member
public @NonNull List<String> getHints() {
@@ -536,7 +556,8 @@
| FLAG_SUPPORTS_FILL_DIALOG
| FLAG_IME_SHOWING
| FLAG_RESET_FILL_DIALOG_STATE
- | FLAG_PCC_DETECTION);
+ | FLAG_PCC_DETECTION
+ | FLAG_SCREEN_HAS_CREDMAN_FIELD);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -558,10 +579,10 @@
};
@DataClass.Generated(
- time = 1675711417112L,
+ time = 1682097266850L,
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_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\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.NonNull java.util.List<java.lang.String> mHints\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)")
+ 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_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\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.NonNull java.util.List<java.lang.String> mHints\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/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index f0140e1..7fa0ac8 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -37,6 +37,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
@@ -140,10 +141,9 @@
private long mCallerMismatchTimeout = 1000;
private long mLastCallerMismatchLog;
- /**
- * Binder that receives calls from the system server.
- */
- private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
+ /** Binder that receives calls from the system server in the content capture flow. */
+ private final IContentCaptureService mContentCaptureServerInterface =
+ new IContentCaptureService.Stub() {
@Override
public void onConnected(IBinder callback, boolean verbose, boolean debug) {
@@ -199,10 +199,24 @@
}
};
- /**
- * Binder that receives calls from the app.
- */
- private final IContentCaptureDirectManager mClientInterface =
+ /** Binder that receives calls from the system server in the content protection flow. */
+ private final IContentProtectionService mContentProtectionServerInterface =
+ new IContentProtectionService.Stub() {
+
+ @Override
+ public void onLoginDetected(
+ @SuppressWarnings("rawtypes") ParceledListSlice events) {
+ mHandler.sendMessage(
+ obtainMessage(
+ ContentCaptureService::handleOnLoginDetected,
+ ContentCaptureService.this,
+ Binder.getCallingUid(),
+ events));
+ }
+ };
+
+ /** Binder that receives calls from the app in the content capture flow. */
+ private final IContentCaptureDirectManager mContentCaptureClientInterface =
new IContentCaptureDirectManager.Stub() {
@Override
@@ -232,9 +246,19 @@
@Override
public final IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mServerInterface.asBinder();
+ return mContentCaptureServerInterface.asBinder();
}
- Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ if (PROTECTION_SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mContentProtectionServerInterface.asBinder();
+ }
+ Log.w(
+ TAG,
+ "Tried to bind to wrong intent (should be "
+ + SERVICE_INTERFACE
+ + " or "
+ + PROTECTION_SERVICE_INTERFACE
+ + "): "
+ + intent);
return null;
}
@@ -468,7 +492,7 @@
} else {
stateFlags |= ContentCaptureSession.STATE_DISABLED;
}
- setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
+ setClientState(clientReceiver, stateFlags, mContentCaptureClientInterface.asBinder());
}
private void handleSendEvents(int uid,
@@ -536,6 +560,18 @@
writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
}
+ private void handleOnLoginDetected(
+ int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
+ if (uid != Process.SYSTEM_UID) {
+ Log.e(TAG, "handleOnLoginDetected() not allowed for uid: " + uid);
+ return;
+ }
+ List<ContentCaptureEvent> events = parceledEvents.getList();
+ int sessionIdInt = events.isEmpty() ? NO_SESSION_ID : events.get(0).getSessionId();
+ ContentCaptureSessionId sessionId = new ContentCaptureSessionId(sessionIdInt);
+ events.forEach(event -> onContentCaptureEvent(sessionId, event));
+ }
+
private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
}
diff --git a/core/java/android/service/contentcapture/IContentProtectionService.aidl b/core/java/android/service/contentcapture/IContentProtectionService.aidl
new file mode 100644
index 0000000..4a13c3f
--- /dev/null
+++ b/core/java/android/service/contentcapture/IContentProtectionService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.contentcapture;
+
+import android.content.pm.ParceledListSlice;
+import android.view.contentcapture.ContentCaptureEvent;
+
+/**
+ * Interface from the system server to the content protection service.
+ *
+ * @hide
+ */
+oneway interface IContentProtectionService {
+
+ void onLoginDetected(in ParceledListSlice events);
+}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 5469916..9a02b74b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -71,13 +71,7 @@
@Override
public void wakeUp() {
- mService.wakeUp(this, () -> {
- try {
- mDreamOverlayCallback.onWakeUpComplete();
- } catch (RemoteException e) {
- Log.e(TAG, "Could not notify dream of wakeUp", e);
- }
- });
+ mService.wakeUp(this);
}
@Override
@@ -125,14 +119,14 @@
mCurrentClient = null;
}
- private void wakeUp(OverlayClient client, Runnable callback) {
+ private void wakeUp(OverlayClient client) {
// Run on executor as this is a binder call from OverlayClient.
mExecutor.execute(() -> {
if (mCurrentClient != client) {
return;
}
- onWakeUp(callback);
+ onWakeUp();
});
}
@@ -190,19 +184,10 @@
/**
* This method is overridden by implementations to handle when the dream has been requested
- * to wakeup. This allows any overlay animations to run. By default, the method will invoke
- * the callback immediately.
- *
- * This callback will be run on the {@link Executor} provided in the constructor if provided, or
- * on the main executor if none was provided.
- *
- * @param onCompleteCallback The callback to trigger to notify the dream service that the
- * overlay has completed waking up.
+ * to wakeup.
* @hide
*/
- public void onWakeUp(@NonNull Runnable onCompleteCallback) {
- onCompleteCallback.run();
- }
+ public void onWakeUp() {}
/**
* This method is overridden by implementations to handle when the dream has ended. There may
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 3a32352..cd57de5 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -249,13 +249,6 @@
// Simply finish dream when exit is requested.
mHandler.post(() -> finish());
}
-
- @Override
- public void onWakeUpComplete() {
- // Finish the dream once overlay animations are complete. Execute on handler since
- // this is coming in on the overlay binder.
- mHandler.post(() -> finish());
- }
};
@@ -923,6 +916,7 @@
overlay.wakeUp();
} catch (RemoteException e) {
Slog.e(TAG, "Error waking the overlay service", e);
+ } finally {
finish();
}
});
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
index 4ad63f1..ec76a33 100644
--- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -28,7 +28,4 @@
* Invoked to request the dream exit.
*/
void onExitRequested();
-
- /** Invoked when the dream overlay wakeUp animation is complete. */
- void onWakeUpComplete();
}
\ No newline at end of file
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index d87198a0..ff7d8bb 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -238,7 +238,7 @@
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "true");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
- DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "true");
+ DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index b15169f..ca3dff3 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,6 +693,7 @@
* @throws IllegalStateException if no frame is in progress.
* @hide
*/
+ @TestApi
@UnsupportedAppUsage
public long getFrameTimeNanos() {
synchronized (mLock) {
@@ -716,6 +717,27 @@
}
}
+ /**
+ * Gets the time in {@link System#nanoTime()} timebase which the current frame
+ * is expected to be presented.
+ * <p>
+ * This time should be used to advance any animation clocks.
+ * Prefer using this method over {@link #getFrameTimeNanos()}.
+ * </p><p>
+ * This method should only be called from within a callback.
+ * </p>
+ *
+ * @return The frame start time, in the {@link System#nanoTime()} time base.
+ *
+ * @throws IllegalStateException if no frame is in progress.
+ * @hide
+ */
+ @TestApi
+ @UnsupportedAppUsage
+ public long getExpectedPresentationTimeNanos() {
+ return mFrameData.getPreferredFrameTimeline().getExpectedPresentationTimeNanos();
+ }
+
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
@@ -1207,6 +1229,10 @@
if (newPreferredDeadline < minimumDeadline) {
DisplayEventReceiver.VsyncEventData latestVsyncEventData =
displayEventReceiver.getLatestVsyncEventData();
+ if (latestVsyncEventData == null) {
+ throw new IllegalArgumentException(
+ "Could not get VsyncEventData. Did SurfaceFlinger crash?");
+ }
update(frameTimeNanos, latestVsyncEventData);
} else {
update(frameTimeNanos, newPreferredIndex);
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index d7d0d08..effc127 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -151,10 +151,11 @@
private SurfaceControl mSurfaceControl;
private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
private final IBinder mInputToken;
+ @NonNull
private final ISurfaceControlViewHost mRemoteInterface;
SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
- IBinder inputToken, ISurfaceControlViewHost ri) {
+ IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
mSurfaceControl = sc;
mAccessibilityEmbeddedConnection = connection;
mInputToken = inputToken;
@@ -216,6 +217,7 @@
/**
* @hide
*/
+ @NonNull
public ISurfaceControlViewHost getRemoteInterface() {
return mRemoteInterface;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 38e3526..d0ab8a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10411,11 +10411,6 @@
return false;
}
- // Disable triggering autofill if the view is integrated with CredentialManager.
- if (afm.shouldIgnoreCredentialViews() && isCredential()) {
- return false;
- }
-
// Check whether view is not part of an activity. If it's not, return false.
if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
return false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a360fb5..f21851f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5462,7 +5462,7 @@
}
private void updateRenderHdrSdrRatio() {
- mRenderHdrSdrRatio = mDisplay.getHdrSdrRatio();
+ mRenderHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mDisplay.getHdrSdrRatio());
mUpdateHdrSdrRatioInfo = true;
}
@@ -5490,22 +5490,14 @@
mHdrSdrRatioChangedListener = null;
} else {
mHdrSdrRatioChangedListener = display -> {
- setTargetHdrSdrRatio(display.getHdrSdrRatio());
+ updateRenderHdrSdrRatio();
+ invalidate();
};
mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
}
}
}
- /** happylint */
- public void setTargetHdrSdrRatio(float ratio) {
- if (mRenderHdrSdrRatio != ratio) {
- mRenderHdrSdrRatio = ratio;
- mUpdateHdrSdrRatioInfo = true;
- invalidate();
- }
- }
-
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 106a77b..956230d 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -99,7 +99,6 @@
"autofill_dialog_hints";
// START CREDENTIAL MANAGER FLAGS //
-
/**
* Indicates whether credential manager tagged views should be ignored from autofill structures.
* This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
@@ -116,22 +115,13 @@
"autofill_credential_manager_enabled";
/**
- * Indicates whether credential manager tagged views should suppress fill dialog.
+ * Indicates whether credential manager tagged views should suppress fill and save dialog.
* This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
*
* @hide
*/
- public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG =
- "autofill_credential_manager_suppress_fill_dialog";
-
- /**
- * Indicates whether credential manager tagged views should suppress save dialog.
- * This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
- *
- * @hide
- */
- public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_SAVE_DIALOG =
- "autofill_credential_manager_suppress_save_dialog";
+ public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG =
+ "autofill_credential_manager_suppress_fill_and_save_dialog";
// END CREDENTIAL MANAGER FLAGS //
// START AUTOFILL FOR ALL APPS FLAGS //
@@ -272,9 +262,7 @@
// CREDENTIAL MANAGER DEFAULTS
// Credential manager is enabled by default so as to allow testing by app developers
private static final boolean DEFAULT_CREDENTIAL_MANAGER_ENABLED = true;
- private static final boolean DEFAULT_CREDENTIAL_MANAGER_IGNORE_VIEWS = true;
- private static final boolean DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG = false;
- private static final boolean DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_SAVE_DIALOG = false;
+ private static final boolean DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG = true;
// END CREDENTIAL MANAGER DEFAULTS
@@ -328,6 +316,7 @@
(str) -> !TextUtils.isEmpty(str));
}
+ /* starts credman flag getter function */
/**
* Whether the Credential Manager feature is enabled or not
*
@@ -341,30 +330,17 @@
}
/**
- * Whether credential manager tagged views should be ignored for autofill structure.
- *
- * @hide
- */
- public static boolean shouldIgnoreCredentialViews() {
- return isCredentialManagerEnabled()
- && DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS,
- DEFAULT_CREDENTIAL_MANAGER_IGNORE_VIEWS);
- }
-
- /**
* Whether credential manager tagged views should not trigger fill dialog requests.
*
* @hide
*/
- public static boolean isFillDialogDisabledForCredentialManager() {
- return isCredentialManagerEnabled()
- && DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG,
- DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG);
+ public static boolean isFillAndSaveDialogDisabledForCredentialManager() {
+ return isCredentialManagerEnabled() && DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG,
+ DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG);
}
+ /* ends credman flag getter function */
/**
* Whether triggering fill request on unimportant view is enabled.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 927833e..792b38b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -22,6 +22,7 @@
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_PCC_DETECTION;
import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
+import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
@@ -665,6 +666,8 @@
@GuardedBy("mLock")
@Nullable private Executor mRequestCallbackExecutor;
+ private boolean mScreenHasCredmanField;
+
/**
* Indicates whether there is already a field to do a fill request after
* the activity started.
@@ -680,6 +683,8 @@
private final boolean mIsFillDialogEnabled;
+ private final boolean mIsFillAndSaveDialogDisabledForCredentialManager;
+
// Indicate whether trigger fill request on unimportant views is enabled
private boolean mIsTriggerFillRequestOnUnimportantViewEnabled = false;
@@ -876,7 +881,10 @@
mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled();
mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints();
- mShouldIgnoreCredentialViews = AutofillFeatureFlags.shouldIgnoreCredentialViews();
+
+ mIsFillAndSaveDialogDisabledForCredentialManager =
+ AutofillFeatureFlags.isFillAndSaveDialogDisabledForCredentialManager();
+
if (sDebug) {
Log.d(TAG, "Fill dialog is enabled:" + mIsFillDialogEnabled
+ ", hints=" + Arrays.toString(mFillDialogEnabledHints));
@@ -1443,12 +1451,12 @@
if (infos.size() == 0) {
throw new IllegalArgumentException("No VirtualViewInfo found");
}
- if (AutofillFeatureFlags.isFillDialogDisabledForCredentialManager()
- && view.isCredential()) {
+ if (view.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+ view.getAutofillId().toString());
}
+ mScreenHasCredmanField = true;
return;
}
for (int i = 0; i < infos.size(); i++) {
@@ -1470,12 +1478,13 @@
if (sDebug) {
Log.d(TAG, "notifyViewEnteredForFillDialog:" + v.getAutofillId());
}
- if (AutofillFeatureFlags.isFillDialogDisabledForCredentialManager()
- && v.isCredential()) {
+ if (v.isCredential()
+ && mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
- + v.getAutofillId().toString());
+ + v.getAutofillId());
}
+ mScreenHasCredmanField = true;
return;
}
notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
@@ -1535,7 +1544,7 @@
if (mIsFillDialogEnabled
|| ArrayUtils.containsAny(autofillHints, mFillDialogEnabledHints)) {
if (sDebug) {
- Log.d(TAG, "Trigger fill request when the view is ready.");
+ Log.d(TAG, "Triggering pre-emptive request for fill dialog.");
}
int flags = FLAG_SUPPORTS_FILL_DIALOG;
@@ -1772,6 +1781,15 @@
flags |= FLAG_PASSWORD_INPUT_TYPE;
}
+ // Update session when screen has credman field
+ if (AutofillFeatureFlags.isFillAndSaveDialogDisabledForCredentialManager()
+ && mScreenHasCredmanField) {
+ flags |= FLAG_SCREEN_HAS_CREDMAN_FIELD;
+ if (sVerbose) {
+ Log.v(TAG, "updating session with flag screen has credman view");
+ }
+ }
+
flags |= getImeStateFlag(view);
if (!isActiveLocked()) {
@@ -2072,7 +2090,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get enabled autofill services status.");
+ throw new RuntimeException("Fail to get enabled autofill services status. " + e);
}
}
@@ -2091,7 +2109,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get autofill services component name.");
+ throw new RuntimeException("Fail to get autofill services component name. " + e);
}
}
@@ -2118,7 +2136,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get user data id for field classification.");
+ throw new RuntimeException("Fail to get user data id for field classification. " + e);
}
}
@@ -2141,7 +2159,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get user data for field classification.");
+ throw new RuntimeException("Fail to get user data for field classification. " + e);
}
}
@@ -2181,7 +2199,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get field classification enabled status.");
+ throw new RuntimeException("Fail to get field classification enabled status. " + e);
}
}
@@ -2205,7 +2223,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get default field classification algorithm.");
+ throw new RuntimeException("Fail to get default field classification algorithm. " + e);
}
}
@@ -2227,7 +2245,8 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get available field classification algorithms.");
+ throw new
+ RuntimeException("Fail to get available field classification algorithms. " + e);
}
}
@@ -2251,7 +2270,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
- throw new RuntimeException("Fail to get autofill supported status.");
+ throw new RuntimeException("Fail to get autofill supported status. " + e);
}
}
@@ -2523,6 +2542,7 @@
mIsFillRequested.set(false);
mShowAutofillDialogCalled = false;
mFillDialogTriggerIds = null;
+ mScreenHasCredmanField = false;
mAllTrackedViews.clear();
if (resetEnteredIds) {
mEnteredIds = null;
@@ -3636,7 +3656,8 @@
private boolean shouldShowAutofillDialog(View view, AutofillId id) {
if (!hasFillDialogUiFeature()
|| mShowAutofillDialogCalled
- || mFillDialogTriggerIds == null) {
+ || mFillDialogTriggerIds == null
+ || mScreenHasCredmanField) {
return false;
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index b229106..dc3d323 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -181,8 +181,6 @@
public static final int FLUSH_REASON_VIEW_TREE_APPEARING = 9;
/** @hide */
public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
- /** @hide */
- public static final int FLUSH_REASON_LOGIN_DETECTED = 11;
/**
* After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
@@ -205,8 +203,7 @@
FLUSH_REASON_SESSION_CONNECTED,
FLUSH_REASON_FORCE_FLUSH,
FLUSH_REASON_VIEW_TREE_APPEARING,
- FLUSH_REASON_VIEW_TREE_APPEARED,
- FLUSH_REASON_LOGIN_DETECTED
+ FLUSH_REASON_VIEW_TREE_APPEARED
})
@Retention(RetentionPolicy.SOURCE)
public @interface FlushReason {}
@@ -690,8 +687,6 @@
return "VIEW_TREE_APPEARING";
case FLUSH_REASON_VIEW_TREE_APPEARED:
return "VIEW_TREE_APPEARED";
- case FLUSH_REASON_LOGIN_DETECTED:
- return "LOGIN_DETECTED";
default:
return "UNKNOWN-" + reason;
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index efd50e7..2241fd5 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -52,8 +52,10 @@
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentprotection.ContentProtectionEventProcessor;
import android.view.inputmethod.BaseInputConnection;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import java.io.PrintWriter;
@@ -118,9 +120,13 @@
/**
* Direct interface to the service binder object - it's used to send the events, including the
* last ones (when the session is finished)
+ *
+ * @hide
*/
- @NonNull
- private IContentCaptureDirectManager mDirectServiceInterface;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public IContentCaptureDirectManager mDirectServiceInterface;
+
@Nullable
private DeathRecipient mDirectServiceVulture;
@@ -131,14 +137,19 @@
@Nullable
private IBinder mShareableActivityToken;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
- private ComponentName mComponentName;
+ public ComponentName mComponentName;
/**
* List of events held to be sent as a batch.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
- private ArrayList<ContentCaptureEvent> mEvents;
+ public ArrayList<ContentCaptureEvent> mEvents;
// Used just for debugging purposes (on dump)
private long mNextFlush;
@@ -157,6 +168,11 @@
@NonNull
private final SessionStateReceiver mSessionStateReceiver;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
private static class SessionStateReceiver extends IResultReceiver.Stub {
private final WeakReference<MainContentCaptureSession> mMainSession;
@@ -194,8 +210,12 @@
}
}
- protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
- @NonNull ContentCaptureManager manager, @NonNull Handler handler,
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public MainContentCaptureSession(
+ @NonNull ContentCaptureManager.StrippedContext context,
+ @NonNull ContentCaptureManager manager,
+ @NonNull Handler handler,
@NonNull IContentCaptureManager systemServerInterface) {
mContext = context;
mManager = manager;
@@ -273,15 +293,16 @@
}
/**
- * Callback from {@code system_server} after call to
- * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
- * IResultReceiver)}.
+ * Callback from {@code system_server} after call to {@link
+ * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
*
* @param resultCode session state
* @param binder handle to {@code IContentCaptureDirectManager}
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+ public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
if (binder != null) {
mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
mDirectServiceVulture = () -> {
@@ -296,6 +317,20 @@
}
}
+ // Should not be possible for mComponentName to be null here but check anyway
+ if (mManager.mOptions.contentProtectionOptions.enableReceiver
+ && mManager.getContentProtectionEventBuffer() != null
+ && mComponentName != null) {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mManager.getContentProtectionEventBuffer(),
+ mHandler,
+ mSystemServerInterface,
+ mComponentName.getPackageName());
+ } else {
+ mContentProtectionEventProcessor = null;
+ }
+
if ((resultCode & STATE_DISABLED) != 0) {
resetSession(resultCode);
} else {
@@ -311,8 +346,10 @@
}
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void sendEvent(@NonNull ContentCaptureEvent event) {
+ public void sendEvent(@NonNull ContentCaptureEvent event) {
sendEvent(event, /* forceFlush= */ false);
}
@@ -337,6 +374,25 @@
if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
return;
}
+
+ if (isContentProtectionReceiverEnabled()) {
+ sendContentProtectionEvent(event);
+ }
+ if (isContentCaptureReceiverEnabled()) {
+ sendContentCaptureEvent(event, forceFlush);
+ }
+ }
+
+ @UiThread
+ private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+ if (mContentProtectionEventProcessor != null) {
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+ }
+
+ @UiThread
+ private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ final int eventType = event.getType();
final int maxBufferSize = mManager.mOptions.maxBufferSize;
if (mEvents == null) {
if (sVerbose) {
@@ -528,9 +584,11 @@
flush(reason);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
@UiThread
- void flush(@FlushReason int reason) {
+ public void flush(@FlushReason int reason) {
if (mEvents == null || mEvents.size() == 0) {
if (sVerbose) {
Log.v(TAG, "Don't flush for empty event buffer.");
@@ -544,6 +602,10 @@
return;
}
+ if (!isContentCaptureReceiverEnabled()) {
+ return;
+ }
+
if (mDirectServiceInterface == null) {
if (sVerbose) {
Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
@@ -607,8 +669,10 @@
return new ParceledListSlice<>(events);
}
+ /** hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void destroySession() {
+ public void destroySession() {
if (sDebug) {
Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -626,12 +690,15 @@
mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
}
mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
}
// TODO(b/122454205): once we support multiple sessions, we might need to move some of these
// clearings out.
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void resetSession(int newState) {
+ public void resetSession(int newState) {
if (sVerbose) {
Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+ getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -651,6 +718,7 @@
}
}
mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
mHandler.removeMessages(MSG_FLUSH);
}
@@ -878,4 +946,14 @@
private String getDebugState(@FlushReason int reason) {
return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
}
+
+ @UiThread
+ private boolean isContentProtectionReceiverEnabled() {
+ return mManager.mOptions.contentProtectionOptions.enableReceiver;
+ }
+
+ @UiThread
+ private boolean isContentCaptureReceiverEnabled() {
+ return mManager.mOptions.enableReceiver;
+ }
}
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 1d5c6f1..6f229f6 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -20,11 +20,15 @@
import android.os.Handler;
/**
- * Represents a request for handling an SSL error. Instances of this class are
- * created by the WebView and passed to
- * {@link WebViewClient#onReceivedSslError}. The host application must call
- * either {@link #proceed} or {@link #cancel} to set the WebView's response
- * to the request.
+ * Represents a request for handling an SSL error.
+ *
+ * <p>A {@link WebView} creates an instance of this class. The instance is
+ * passed to {@link WebViewClient#onReceivedSslError(WebView, SslErrorHandler,
+ * SslError)}.
+ *
+ * <p>The host application must call {@link #cancel()} or, contrary to secure
+ * web communication standards, {@link #proceed()} to provide the web view's
+ * response to the request.
*/
public class SslErrorHandler extends Handler {
@@ -35,17 +39,29 @@
public SslErrorHandler() {}
/**
- * Proceed with the SSL certificate.
- * <p>
- * It is not recommended to proceed past SSL errors and this method should
- * generally not be used; see {@link WebViewClient#onReceivedSslError} for
- * more information.
+ * Instructs the {@code WebView} that encountered the SSL certificate error
+ * to ignore the error and continue communicating with the server.
+ *
+ * <p class="warning"><b>Warning:</b> When an SSL error occurs, the host
+ * application should always call {@link #cancel()} rather than
+ * {@code proceed()} because an invalid SSL certificate means the connection
+ * is not secure.
+ *
+ * @see WebViewClient#onReceivedSslError(WebView, SslErrorHandler,
+ * SslError)
*/
public void proceed() {}
/**
- * Cancel this request and all pending requests for the WebView that had
- * the error.
+ * Instructs the {@code WebView} that encountered the SSL certificate error
+ * to terminate communication with the server. Cancels the current server
+ * request and all pending requests for the {@code WebView}.
+ *
+ * <p>The host application must call this method to prevent a resource from
+ * loading when an SSL certificate is invalid.
+ *
+ * @see WebViewClient#onReceivedSslError(WebView, SslErrorHandler,
+ * SslError)
*/
public void cancel() {}
}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 55f09f1..2dfeae3 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -382,30 +382,34 @@
}
/**
- * Notify the host application that an SSL error occurred while loading a
- * resource. The host application must call either {@link SslErrorHandler#cancel} or
- * {@link SslErrorHandler#proceed}. Note that the decision may be retained for use in
- * response to future SSL errors. The default behavior is to cancel the
- * load.
- * <p>
- * This API is only called for recoverable SSL certificate errors. In the case of
- * non-recoverable errors (such as when the server fails the client), WebView will call {@link
- * #onReceivedError(WebView, WebResourceRequest, WebResourceError)} with {@link
- * #ERROR_FAILED_SSL_HANDSHAKE}.
- * <p>
- * Applications are advised not to prompt the user about SSL errors, as
- * the user is unlikely to be able to make an informed security decision
- * and WebView does not provide any UI for showing the details of the
- * error in a meaningful way.
- * <p>
- * Application overrides of this method may display custom error pages or
- * silently log issues, but it is strongly recommended to always call
- * {@link SslErrorHandler#cancel} and never allow proceeding past errors.
+ * Notifies the host application that an SSL error occurred while loading a
+ * resource. The host application must call either
+ * {@link SslErrorHandler#cancel()} or {@link SslErrorHandler#proceed()}.
*
- * @param view The WebView that is initiating the callback.
- * @param handler An {@link SslErrorHandler} that will handle the user's
- * response.
- * @param error The SSL error object.
+ * <p class="warning"><b>Warning:</b> Application overrides of this method
+ * can be used to display custom error pages or to silently log issues, but
+ * the host application should always call {@code SslErrorHandler#cancel()}
+ * and never proceed past errors.
+ *
+ * <p class="note"><b>Note:</b> Do not prompt the user about SSL errors.
+ * Users are unlikely to be able to make an informed security decision, and
+ * {@code WebView} does not provide a UI for showing the details of the
+ * error in a meaningful way.
+ *
+ * <p>The decision to call {@code proceed()} or {@code cancel()} may be
+ * retained to facilitate responses to future SSL errors. The default
+ * behavior is to cancel the resource loading process.
+ *
+ * <p>This API is called only for recoverable SSL certificate errors. For
+ * non-recoverable errors (such as when the server fails the client), the
+ * {@code WebView} calls {@link #onReceivedError(WebView,
+ * WebResourceRequest, WebResourceError) onReceivedError(WebView,
+ * WebResourceRequest, WebResourceError)} with the
+ * {@link #ERROR_FAILED_SSL_HANDSHAKE} argument.
+ *
+ * @param view {@code WebView} that initiated the callback.
+ * @param handler {@link SslErrorHandler} that handles the user's response.
+ * @param error SSL error object.
*/
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 57cc38c..0ea8014 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -59,6 +59,8 @@
public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 6;
/** value for INVOCATION_TYPE_KEY: press on physcial assistant button */
public static final int INVOCATION_TYPE_ASSIST_BUTTON = 7;
+ /** value for INVOCATION_TYPE_KEY: long press on nav handle */
+ public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS = 8;
private final Context mContext;
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index bf26568..4e7bfe5 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -16,42 +16,50 @@
package com.android.internal.app;
-import static android.graphics.PixelFormat.TRANSLUCENT;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
import android.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
+import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.CombinedVibration;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.VibrationEffect;
+import android.os.VibratorManager;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.widget.AnalogClock;
+import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.R;
import org.json.JSONObject;
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
+import java.util.Random;
/**
* @hide
@@ -59,30 +67,160 @@
public class PlatLogoActivity extends Activity {
private static final String TAG = "PlatLogoActivity";
- private static final String S_EGG_UNLOCK_SETTING = "egg_mode_s";
+ private static final long LAUNCH_TIME = 5000L;
- private SettableAnalogClock mClock;
+ private static final String U_EGG_UNLOCK_SETTING = "egg_mode_u";
+
+ private static final float MIN_WARP = 1f;
+ private static final float MAX_WARP = 10f; // after all these years
+ private static final boolean FINISH_AFTER_NEXT_STAGE_LAUNCH = false;
+
private ImageView mLogo;
- private BubblesDrawable mBg;
+ private Starfield mStarfield;
+
+ private FrameLayout mLayout;
+
+ private TimeAnimator mAnim;
+ private ObjectAnimator mWarpAnim;
+ private Random mRandom;
+ private float mDp;
+
+ private RumblePack mRumble;
+
+ private boolean mAnimationsEnabled = true;
+
+ private final View.OnTouchListener mTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ measureTouchPressure(event);
+ startWarp();
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ stopWarp();
+ break;
+ }
+ return true;
+ }
+
+ };
+
+ private final Runnable mLaunchNextStage = () -> {
+ stopWarp();
+ launchNextStage(false);
+ };
+
+ private final TimeAnimator.TimeListener mTimeListener = new TimeAnimator.TimeListener() {
+ @Override
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ mStarfield.update(deltaTime);
+ final float warpFrac = (mStarfield.getWarp() - MIN_WARP) / (MAX_WARP - MIN_WARP);
+ if (mAnimationsEnabled) {
+ mLogo.setTranslationX(mRandom.nextFloat() * warpFrac * 5 * mDp);
+ mLogo.setTranslationY(mRandom.nextFloat() * warpFrac * 5 * mDp);
+ }
+ if (warpFrac > 0f) {
+ mRumble.rumble(warpFrac);
+ }
+ mLayout.postInvalidate();
+ }
+ };
+
+ private class RumblePack implements Handler.Callback {
+ private static final int MSG = 6464;
+ private static final int INTERVAL = 50;
+
+ private final VibratorManager mVibeMan;
+ private final HandlerThread mVibeThread;
+ private final Handler mVibeHandler;
+ private boolean mSpinPrimitiveSupported;
+
+ private long mLastVibe = 0;
+
+ @SuppressLint("MissingPermission")
+ @Override
+ public boolean handleMessage(Message msg) {
+ final float warpFrac = msg.arg1 / 100f;
+ if (mSpinPrimitiveSupported) {
+ if (msg.getWhen() > mLastVibe + INTERVAL) {
+ mLastVibe = msg.getWhen();
+ mVibeMan.vibrate(CombinedVibration.createParallel(
+ VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_SPIN, (float) Math.pow(warpFrac, 3.0))
+ .compose()
+ ));
+ }
+ } else {
+ if (mRandom.nextFloat() < warpFrac) {
+ mLogo.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+ }
+ }
+ return false;
+ }
+ RumblePack() {
+ mVibeMan = getSystemService(VibratorManager.class);
+ mSpinPrimitiveSupported = mVibeMan.getDefaultVibrator()
+ .areAllPrimitivesSupported(PRIMITIVE_SPIN);
+
+ mVibeThread = new HandlerThread("VibratorThread");
+ mVibeThread.start();
+ mVibeHandler = Handler.createAsync(mVibeThread.getLooper(), this);
+ }
+
+ public void destroy() {
+ mVibeThread.quit();
+ }
+
+ private void rumble(float warpFrac) {
+ if (!mVibeThread.isAlive()) return;
+
+ final Message msg = Message.obtain();
+ msg.what = MSG;
+ msg.arg1 = (int) (warpFrac * 100);
+ mVibeHandler.removeMessages(MSG);
+ mVibeHandler.sendMessage(msg);
+ }
+
+ }
@Override
- protected void onPause() {
- super.onPause();
+ protected void onDestroy() {
+ mRumble.destroy();
+
+ super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().setDecorFitsSystemWindows(false);
getWindow().setNavigationBarColor(0);
getWindow().setStatusBarColor(0);
+ getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
final ActionBar ab = getActionBar();
if (ab != null) ab.hide();
- final FrameLayout layout = new FrameLayout(this);
+ try {
+ mAnimationsEnabled = Settings.Global.getFloat(getContentResolver(),
+ Settings.Global.ANIMATOR_DURATION_SCALE) > 0f;
+ } catch (Settings.SettingNotFoundException e) {
+ mAnimationsEnabled = true;
+ }
- mClock = new SettableAnalogClock(this);
+ mRumble = new RumblePack();
+
+ mLayout = new FrameLayout(this);
+ mRandom = new Random();
+ mDp = getResources().getDisplayMetrics().density;
+ mStarfield = new Starfield(mRandom, mDp * 2f);
+ mStarfield.setVelocity(
+ 200f * (mRandom.nextFloat() - 0.5f),
+ 200f * (mRandom.nextFloat() - 0.5f));
+ mLayout.setBackground(mStarfield);
final DisplayMetrics dm = getResources().getDisplayMetrics();
final float dp = dm.density;
@@ -90,22 +228,79 @@
final int widgetSize = (int) (minSide * 0.75);
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(widgetSize, widgetSize);
lp.gravity = Gravity.CENTER;
- layout.addView(mClock, lp);
mLogo = new ImageView(this);
- mLogo.setVisibility(View.GONE);
mLogo.setImageResource(R.drawable.platlogo);
- layout.addView(mLogo, lp);
+ mLogo.setOnTouchListener(mTouchListener);
+ mLogo.requestFocus();
+ mLayout.addView(mLogo, lp);
- mBg = new BubblesDrawable();
- mBg.setLevel(0);
- mBg.avoid = widgetSize / 2;
- mBg.padding = 0.5f * dp;
- mBg.minR = 1 * dp;
- layout.setBackground(mBg);
- layout.setOnLongClickListener(mBg);
+ Log.v(TAG, "Hello");
- setContentView(layout);
+ setContentView(mLayout);
+ }
+
+ private void startAnimating() {
+ mAnim = new TimeAnimator();
+ mAnim.setTimeListener(mTimeListener);
+ mAnim.start();
+ }
+
+ private void stopAnimating() {
+ mAnim.cancel();
+ mAnim = null;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_SPACE) {
+ if (event.getRepeatCount() == 0) {
+ startWarp();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_SPACE) {
+ stopWarp();
+ return true;
+ }
+ return false;
+ }
+
+ private void startWarp() {
+ stopWarp();
+ mWarpAnim = ObjectAnimator.ofFloat(mStarfield, "warp", MIN_WARP, MAX_WARP)
+ .setDuration(LAUNCH_TIME);
+ mWarpAnim.start();
+
+ mLogo.postDelayed(mLaunchNextStage, LAUNCH_TIME + 1000L);
+ }
+
+ private void stopWarp() {
+ if (mWarpAnim != null) {
+ mWarpAnim.cancel();
+ mWarpAnim.removeAllListeners();
+ mWarpAnim = null;
+ }
+ mStarfield.setWarp(1f);
+ mLogo.removeCallbacks(mLaunchNextStage);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ startAnimating();
+ }
+
+ @Override
+ public void onPause() {
+ stopWarp();
+ stopAnimating();
+ super.onPause();
}
private boolean shouldWriteSettings() {
@@ -113,38 +308,14 @@
}
private void launchNextStage(boolean locked) {
- mClock.animate()
- .alpha(0f).scaleX(0.5f).scaleY(0.5f)
- .withEndAction(() -> mClock.setVisibility(View.GONE))
- .start();
-
- mLogo.setAlpha(0f);
- mLogo.setScaleX(0.5f);
- mLogo.setScaleY(0.5f);
- mLogo.setVisibility(View.VISIBLE);
- mLogo.animate()
- .alpha(1f)
- .scaleX(1f)
- .scaleY(1f)
- .setInterpolator(new OvershootInterpolator())
- .start();
-
- mLogo.postDelayed(() -> {
- final ObjectAnimator anim = ObjectAnimator.ofInt(mBg, "level", 0, 10000);
- anim.setInterpolator(new DecelerateInterpolator(1f));
- anim.start();
- },
- 500
- );
-
final ContentResolver cr = getContentResolver();
try {
if (shouldWriteSettings()) {
- Log.v(TAG, "Saving egg unlock=" + locked);
+ Log.v(TAG, "Saving egg locked=" + locked);
syncTouchPressure();
Settings.System.putLong(cr,
- S_EGG_UNLOCK_SETTING,
+ U_EGG_UNLOCK_SETTING,
locked ? 0 : System.currentTimeMillis());
}
} catch (RuntimeException e) {
@@ -152,14 +323,18 @@
}
try {
- startActivity(new Intent(Intent.ACTION_MAIN)
+ final Intent eggActivity = new Intent(Intent.ACTION_MAIN)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .addCategory("com.android.internal.category.PLATLOGO"));
+ .addCategory("com.android.internal.category.PLATLOGO");
+ Log.v(TAG, "launching: " + eggActivity);
+ startActivity(eggActivity);
} catch (ActivityNotFoundException ex) {
Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs.");
}
- //finish(); // no longer finish upon unlock; it's fun to frob the dial
+ if (FINISH_AFTER_NEXT_STAGE_LAUNCH) {
+ finish(); // we're done here.
+ }
}
static final String TOUCH_STATS = "touch.stats";
@@ -217,266 +392,111 @@
super.onStop();
}
- /**
- * Subclass of AnalogClock that allows the user to flip up the glass and adjust the hands.
- */
- public class SettableAnalogClock extends AnalogClock {
- private int mOverrideHour = -1;
- private int mOverrideMinute = -1;
- private boolean mOverride = false;
+ private static class Starfield extends Drawable {
+ private static final int NUM_STARS = 34; // Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- public SettableAnalogClock(Context context) {
- super(context);
+ private static final int NUM_PLANES = 2;
+ private final float[] mStars = new float[NUM_STARS * 4];
+ private float mVx, mVy;
+ private long mDt = 0;
+ private final Paint mStarPaint;
+
+ private final Random mRng;
+ private final float mSize;
+
+ private final Rect mSpace = new Rect();
+ private float mWarp = 1f;
+
+ private float mBuffer;
+
+ public void setWarp(float warp) {
+ mWarp = warp;
+ }
+
+ public float getWarp() {
+ return mWarp;
+ }
+
+ Starfield(Random rng, float size) {
+ mRng = rng;
+ mSize = size;
+ mStarPaint = new Paint();
+ mStarPaint.setStyle(Paint.Style.STROKE);
+ mStarPaint.setColor(Color.WHITE);
}
@Override
- protected Instant now() {
- final Instant realNow = super.now();
- final ZoneId tz = Clock.systemDefaultZone().getZone();
- final ZonedDateTime zdTime = realNow.atZone(tz);
- if (mOverride) {
- if (mOverrideHour < 0) {
- mOverrideHour = zdTime.getHour();
- }
- return Clock.fixed(zdTime
- .withHour(mOverrideHour)
- .withMinute(mOverrideMinute)
- .withSecond(0)
- .toInstant(), tz).instant();
- } else {
- return realNow;
+ public void onBoundsChange(Rect bounds) {
+ mSpace.set(bounds);
+ mBuffer = mSize * NUM_PLANES * 2 * MAX_WARP;
+ mSpace.inset(-(int) mBuffer, -(int) mBuffer);
+ final float w = mSpace.width();
+ final float h = mSpace.height();
+ for (int i = 0; i < NUM_STARS; i++) {
+ mStars[4 * i] = mRng.nextFloat() * w;
+ mStars[4 * i + 1] = mRng.nextFloat() * h;
+ mStars[4 * i + 2] = mStars[4 * i];
+ mStars[4 * i + 3] = mStars[4 * i + 1];
}
}
- double toPositiveDegrees(double rad) {
- return (Math.toDegrees(rad) + 360 - 90) % 360;
+ public void setVelocity(float x, float y) {
+ mVx = x;
+ mVy = y;
}
@Override
- public boolean onTouchEvent(MotionEvent ev) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mOverride = true;
- // pass through
- case MotionEvent.ACTION_MOVE:
- measureTouchPressure(ev);
+ public void draw(@NonNull Canvas canvas) {
+ final float dtSec = mDt / 1000f;
+ final float dx = (mVx * dtSec * mWarp);
+ final float dy = (mVy * dtSec * mWarp);
- float x = ev.getX();
- float y = ev.getY();
- float cx = getWidth() / 2f;
- float cy = getHeight() / 2f;
- float angle = (float) toPositiveDegrees(Math.atan2(x - cx, y - cy));
+ final boolean inWarp = mWarp > 1f;
- int minutes = (75 - (int) (angle / 6)) % 60;
- int minuteDelta = minutes - mOverrideMinute;
- if (minuteDelta != 0) {
- if (Math.abs(minuteDelta) > 45 && mOverrideHour >= 0) {
- int hourDelta = (minuteDelta < 0) ? 1 : -1;
- mOverrideHour = (mOverrideHour + 24 + hourDelta) % 24;
- }
- mOverrideMinute = minutes;
- if (mOverrideMinute == 0) {
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- if (getScaleX() == 1f) {
- setScaleX(1.05f);
- setScaleY(1.05f);
- animate().scaleX(1f).scaleY(1f).setDuration(150).start();
- }
- } else {
- performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
- }
+ canvas.drawColor(Color.BLACK); // 0xFF16161D);
- onTimeChanged();
- postInvalidate();
- }
-
- return true;
- case MotionEvent.ACTION_UP:
- if (mOverrideMinute == 0 && (mOverrideHour % 12) == 1) {
- Log.v(TAG, "13:00");
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- launchNextStage(false);
- }
- return true;
- }
- return false;
- }
- }
-
- private static final String[][] EMOJI_SETS = {
- {"🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🥭", "🍎", "🍏", "🍐", "🍑",
- "🍒", "🍓", "🫐", "🥝"},
- {"😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾"},
- {"😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", "🙂", "🙃", "🫠", "😉", "😊",
- "😇", "🥰", "😍", "🤩", "😘", "😗", "☺️", "😚", "😙", "🥲", "😋", "😛", "😜",
- "🤪", "😝", "🤑", "🤗", "🤭", "🫢", "🫣", "🤫", "🤔", "🫡", "🤐", "🤨", "😐",
- "😑", "😶", "🫥", "😏", "😒", "🙄", "😬", "🤥", "😌", "😔", "😪", "🤤", "😴",
- "😷"},
- { "🤩", "😍", "🥰", "😘", "🥳", "🥲", "🥹" },
- { "🫠" },
- {"💘", "💝", "💖", "💗", "💓", "💞", "💕", "❣", "💔", "❤", "🧡", "💛",
- "💚", "💙", "💜", "🤎", "🖤", "🤍"},
- // {"👁", "️🫦", "👁️"}, // this one is too much
- {"👽", "🛸", "✨", "🌟", "💫", "🚀", "🪐", "🌙", "⭐", "🌍"},
- {"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"},
- {"🐙", "🪸", "🦑", "🦀", "🦐", "🐡", "🦞", "🐠", "🐟", "🐳", "🐋", "🐬", "🫧", "🌊",
- "🦈"},
- {"🙈", "🙉", "🙊", "🐵", "🐒"},
- {"♈", "♉", "♊", "♋", "♌", "♍", "♎", "♏", "♐", "♑", "♒", "♓"},
- {"🕛", "🕧", "🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔", "🕠", "🕕", "🕡",
- "🕖", "🕢", "🕗", "🕣", "🕘", "🕤", "🕙", "🕥", "🕚", "🕦"},
- {"🌺", "🌸", "💮", "🏵️", "🌼", "🌿"},
- {"🐢", "✨", "🌟", "👑"}
- };
-
- static class Bubble {
- public float x, y, r;
- public int color;
- public String text = null;
- }
-
- class BubblesDrawable extends Drawable implements View.OnLongClickListener {
- private static final int MAX_BUBBS = 2000;
-
- private final int[] mColorIds = {
- android.R.color.system_accent3_400,
- android.R.color.system_accent3_500,
- android.R.color.system_accent3_600,
-
- android.R.color.system_accent2_400,
- android.R.color.system_accent2_500,
- android.R.color.system_accent2_600,
- };
-
- private int[] mColors = new int[mColorIds.length];
-
- private int mEmojiSet = -1;
-
- private final Bubble[] mBubbs = new Bubble[MAX_BUBBS];
- private int mNumBubbs;
-
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- public float avoid = 0f;
- public float padding = 0f;
- public float minR = 0f;
-
- BubblesDrawable() {
- for (int i = 0; i < mColorIds.length; i++) {
- mColors[i] = getColor(mColorIds[i]);
- }
- for (int j = 0; j < mBubbs.length; j++) {
- mBubbs[j] = new Bubble();
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (getLevel() == 0) return;
- final float f = getLevel() / 10000f;
- mPaint.setStyle(Paint.Style.FILL);
- mPaint.setTextAlign(Paint.Align.CENTER);
- int drawn = 0;
- for (int j = 0; j < mNumBubbs; j++) {
- if (mBubbs[j].color == 0 || mBubbs[j].r == 0) continue;
- if (mBubbs[j].text != null) {
- mPaint.setTextSize(mBubbs[j].r * 1.75f);
- canvas.drawText(mBubbs[j].text, mBubbs[j].x,
- mBubbs[j].y + mBubbs[j].r * f * 0.6f, mPaint);
- } else {
- mPaint.setColor(mBubbs[j].color);
- canvas.drawCircle(mBubbs[j].x, mBubbs[j].y, mBubbs[j].r * f, mPaint);
- }
- drawn++;
- }
- }
-
- public void chooseEmojiSet() {
- mEmojiSet = (int) (Math.random() * EMOJI_SETS.length);
- final String[] emojiSet = EMOJI_SETS[mEmojiSet];
- for (int j = 0; j < mBubbs.length; j++) {
- mBubbs[j].text = emojiSet[(int) (Math.random() * emojiSet.length)];
- }
- invalidateSelf();
- }
-
- @Override
- protected boolean onLevelChange(int level) {
- invalidateSelf();
- return true;
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- randomize();
- }
-
- private void randomize() {
- final float w = getBounds().width();
- final float h = getBounds().height();
- final float maxR = Math.min(w, h) / 3f;
- mNumBubbs = 0;
- if (avoid > 0f) {
- mBubbs[mNumBubbs].x = w / 2f;
- mBubbs[mNumBubbs].y = h / 2f;
- mBubbs[mNumBubbs].r = avoid;
- mBubbs[mNumBubbs].color = 0;
- mNumBubbs++;
- }
- for (int j = 0; j < MAX_BUBBS; j++) {
- // a simple but time-tested bubble-packing algorithm:
- // 1. pick a spot
- // 2. shrink the bubble until it is no longer overlapping any other bubble
- // 3. if the bubble hasn't popped, keep it
- int tries = 5;
- while (tries-- > 0) {
- float x = (float) Math.random() * w;
- float y = (float) Math.random() * h;
- float r = Math.min(Math.min(x, w - x), Math.min(y, h - y));
-
- // shrink radius to fit other bubbs
- for (int i = 0; i < mNumBubbs; i++) {
- r = (float) Math.min(r,
- Math.hypot(x - mBubbs[i].x, y - mBubbs[i].y) - mBubbs[i].r
- - padding);
- if (r < minR) break;
- }
-
- if (r >= minR) {
- // we have found a spot for this bubble to live, let's save it and move on
- r = Math.min(maxR, r);
-
- mBubbs[mNumBubbs].x = x;
- mBubbs[mNumBubbs].y = y;
- mBubbs[mNumBubbs].r = r;
- mBubbs[mNumBubbs].color = mColors[(int) (Math.random() * mColors.length)];
- mNumBubbs++;
- break;
- }
+ if (mDt > 0 && mDt < 1000) {
+ canvas.translate(
+ -(mBuffer) + mRng.nextFloat() * (mWarp - 1f),
+ -(mBuffer) + mRng.nextFloat() * (mWarp - 1f)
+ );
+ final float w = mSpace.width();
+ final float h = mSpace.height();
+ for (int i = 0; i < NUM_STARS; i++) {
+ final int plane = (int) ((((float) i) / NUM_STARS) * NUM_PLANES) + 1;
+ mStars[4 * i + 2] = (mStars[4 * i + 2] + dx * plane + w) % w;
+ mStars[4 * i + 3] = (mStars[4 * i + 3] + dy * plane + h) % h;
+ mStars[4 * i + 0] = inWarp ? mStars[4 * i + 2] - dx * mWarp * 2 * plane : -100;
+ mStars[4 * i + 1] = inWarp ? mStars[4 * i + 3] - dy * mWarp * 2 * plane : -100;
}
}
- Log.v(TAG, String.format("successfully placed %d bubbles (%d%%)",
- mNumBubbs, (int) (100f * mNumBubbs / MAX_BUBBS)));
+ final int slice = (mStars.length / NUM_PLANES / 4) * 4;
+ for (int p = 0; p < NUM_PLANES; p++) {
+ mStarPaint.setStrokeWidth(mSize * (p + 1));
+ if (inWarp) {
+ canvas.drawLines(mStars, p * slice, slice, mStarPaint);
+ }
+ canvas.drawPoints(mStars, p * slice, slice, mStarPaint);
+ }
}
@Override
- public void setAlpha(int alpha) { }
+ public void setAlpha(int alpha) {
+
+ }
@Override
- public void setColorFilter(ColorFilter colorFilter) { }
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+
+ }
@Override
public int getOpacity() {
- return TRANSLUCENT;
+ return PixelFormat.OPAQUE;
}
- @Override
- public boolean onLongClick(View v) {
- if (getLevel() == 0) return false;
- chooseEmojiSet();
- return true;
+ public void update(long dt) {
+ mDt = dt;
}
}
-
-}
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 4cb592e..9ffccb3 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -118,6 +118,9 @@
*/
public static final String NAS_DEFAULT_SERVICE = "nas_default_service";
+ /** (boolean) Whether notify() calls to NMS should acquire and hold WakeLocks. */
+ public static final String NOTIFY_WAKELOCK = "nms_notify_wakelock";
+
// Flags related to media notifications
/**
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 80d753c..f62ff38 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -24,6 +24,7 @@
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
@@ -545,6 +546,8 @@
return ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
case TimeoutKind.SHORT_FGS_TIMEOUT:
return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
+ case TimeoutKind.JOB_SERVICE:
+ return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
default:
return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
}
diff --git a/core/java/com/android/internal/util/TraceBuffer.java b/core/java/com/android/internal/util/TraceBuffer.java
index fcc77bd4..c23e902 100644
--- a/core/java/com/android/internal/util/TraceBuffer.java
+++ b/core/java/com/android/internal/util/TraceBuffer.java
@@ -18,6 +18,7 @@
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
@@ -39,12 +40,13 @@
* {@hide}
*/
public class TraceBuffer<P, S extends P, T extends P> {
- private final Object mBufferLock = new Object();
-
private final ProtoProvider<P, S, T> mProtoProvider;
+ @GuardedBy("this")
private final Queue<T> mBuffer = new ArrayDeque<>();
private final Consumer mProtoDequeuedCallback;
+ @GuardedBy("this")
private int mBufferUsedSize;
+ @GuardedBy("this")
private int mBufferCapacity;
/**
@@ -115,18 +117,18 @@
resetBuffer();
}
- public int getAvailableSpace() {
+ public synchronized int getAvailableSpace() {
return mBufferCapacity - mBufferUsedSize;
}
/**
* Returns buffer size.
*/
- public int size() {
+ public synchronized int size() {
return mBuffer.size();
}
- public void setCapacity(int capacity) {
+ public synchronized void setCapacity(int capacity) {
mBufferCapacity = capacity;
}
@@ -137,22 +139,19 @@
* @throws IllegalStateException if the element cannot be added because it is larger
* than the buffer size.
*/
- public void add(T proto) {
+ public synchronized void add(T proto) {
int protoLength = mProtoProvider.getItemSize(proto);
if (protoLength > mBufferCapacity) {
throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+ mBufferCapacity + " Object size: " + protoLength);
}
- synchronized (mBufferLock) {
- discardOldest(protoLength);
- mBuffer.add(proto);
- mBufferUsedSize += protoLength;
- mBufferLock.notify();
- }
+ discardOldest(protoLength);
+ mBuffer.add(proto);
+ mBufferUsedSize += protoLength;
}
@VisibleForTesting
- public boolean contains(byte[] other) {
+ public synchronized boolean contains(byte[] other) {
return mBuffer.stream()
.anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other));
}
@@ -160,15 +159,13 @@
/**
* Writes the trace buffer to disk inside the encapsulatingProto.
*/
- public void writeTraceToFile(File traceFile, S encapsulatingProto)
+ public synchronized void writeTraceToFile(File traceFile, S encapsulatingProto)
throws IOException {
- synchronized (mBufferLock) {
- traceFile.delete();
- try (OutputStream os = new FileOutputStream(traceFile)) {
- traceFile.setReadable(true /* readable */, false /* ownerOnly */);
- mProtoProvider.write(encapsulatingProto, mBuffer, os);
- os.flush();
- }
+ traceFile.delete();
+ try (OutputStream os = new FileOutputStream(traceFile)) {
+ traceFile.setReadable(true /* readable */, false /* ownerOnly */);
+ mProtoProvider.write(encapsulatingProto, mBuffer, os);
+ os.flush();
}
}
@@ -199,31 +196,27 @@
/**
* Removes all elements from the buffer
*/
- public void resetBuffer() {
- synchronized (mBufferLock) {
- if (mProtoDequeuedCallback != null) {
- for (T item : mBuffer) {
- mProtoDequeuedCallback.accept(item);
- }
+ public synchronized void resetBuffer() {
+ if (mProtoDequeuedCallback != null) {
+ for (T item : mBuffer) {
+ mProtoDequeuedCallback.accept(item);
}
- mBuffer.clear();
- mBufferUsedSize = 0;
}
+ mBuffer.clear();
+ mBufferUsedSize = 0;
}
@VisibleForTesting
- public int getBufferSize() {
+ public synchronized int getBufferSize() {
return mBufferUsedSize;
}
/**
* Returns the buffer status in human-readable form.
*/
- public String getStatus() {
- synchronized (mBufferLock) {
- return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
- + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
- + "Elements in the buffer: " + mBuffer.size();
- }
+ public synchronized String getStatus() {
+ return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
+ + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
+ + "Elements in the buffer: " + mBuffer.size();
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index ddd58bc..12fd6c5 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -47,6 +47,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -160,9 +161,17 @@
*/
public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
+ /**
+ * Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
+ * method writes the password data to the repair mode file after the credential is verified
+ * successfully.
+ */
+ public static final int VERIFY_FLAG_WRITE_REPAIR_MODE_PW = 1 << 1;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
- VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+ VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+ VERIFY_FLAG_WRITE_REPAIR_MODE_PW
})
public @interface VerifyFlag {}
@@ -171,6 +180,11 @@
*/
public static final int USER_FRP = UserHandle.USER_NULL + 1;
+ /**
+ * Special user id for triggering the exiting repair mode verification flow.
+ */
+ public static final int USER_REPAIR_MODE = UserHandle.USER_NULL + 2;
+
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
@Deprecated
public final static String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
@@ -200,6 +214,8 @@
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
public static final String PASSWORD_HISTORY_DELIMITER = ",";
+ private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
+
/**
* drives the pin auto confirmation feature availability in code logic.
*/
@@ -388,7 +404,7 @@
@UnsupportedAppUsage
public void reportFailedPasswordAttempt(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+ if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
return;
}
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -397,7 +413,7 @@
@UnsupportedAppUsage
public void reportSuccessfulPasswordAttempt(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+ if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
return;
}
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -405,21 +421,21 @@
}
public void reportPasswordLockout(int timeoutMs, int userId) {
- if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+ if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
return;
}
getTrustManager().reportUnlockLockout(timeoutMs, userId);
}
public int getCurrentFailedPasswordAttempts(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+ if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
return 0;
}
return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
}
public int getMaximumFailedPasswordsForWipe(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+ if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
return 0;
}
return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -739,6 +755,17 @@
}
}
+ /** Returns the credential type corresponding to the given PIN or password quality. */
+ public static int pinOrPasswordQualityToCredentialType(int quality) {
+ if (isQualityAlphabeticPassword(quality)) {
+ return CREDENTIAL_TYPE_PASSWORD;
+ }
+ if (isQualityNumericPin(quality)) {
+ return CREDENTIAL_TYPE_PIN;
+ }
+ throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
+ }
+
/**
* Save a new lockscreen credential.
*
@@ -973,7 +1000,7 @@
}
@Override
public boolean shouldBypassCache(Integer userHandle) {
- return userHandle == USER_FRP;
+ return isSpecialUserId(userHandle);
}
};
@@ -1080,9 +1107,10 @@
@UnsupportedAppUsage
public long setLockoutAttemptDeadline(int userId, int timeoutMs) {
final long deadline = SystemClock.elapsedRealtime() + timeoutMs;
- if (userId == USER_FRP) {
- // For secure password storage (that is required for FRP), the underlying storage also
- // enforces the deadline. Since we cannot store settings for the FRP user, don't.
+ if (isSpecialUserId(userId)) {
+ // For secure password storage (that is required for special users such as FRP), the
+ // underlying storage also enforces the deadline. Since we cannot store settings
+ // for special users, don't.
return deadline;
}
mLockoutDeadlines.put(userId, deadline);
@@ -1822,6 +1850,64 @@
}
/**
+ * Return {@code true} if repair mode is supported by the device.
+ */
+ public static boolean isRepairModeSupported(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_repairModeSupported);
+ }
+
+ /**
+ * Return {@code true} if repair mode is active on the device.
+ */
+ public static boolean isRepairModeActive(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REPAIR_MODE_ACTIVE, /* def= */ 0) > 0;
+ }
+
+ /**
+ * Return {@code true} if repair mode is supported by the device and the user has been granted
+ * admin privileges.
+ */
+ public static boolean canUserEnterRepairMode(Context context, UserInfo info) {
+ return info != null && info.isAdmin() && isRepairModeSupported(context);
+ }
+
+ /**
+ * Return {@code true} if GSI is running on the device.
+ */
+ public static boolean isGsiRunning() {
+ return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+ }
+
+ /**
+ * Return {@code true} if the given user id is a special user such as {@link #USER_FRP}.
+ */
+ public static boolean isSpecialUserId(int userId) {
+ return isSpecialUserId(/* context= */ null, userId, /* checkDeviceSupported= */ false);
+ }
+
+ /**
+ * Return {@code true} if the given user id is a special user for the verification flow.
+ *
+ * @param checkDeviceSupported {@code true} to check the specified user is supported
+ * by the device.
+ */
+ private static boolean isSpecialUserId(@Nullable Context context, int userId,
+ boolean checkDeviceSupported) {
+ switch (userId) {
+ case USER_FRP:
+ if (checkDeviceSupported) return frpCredentialEnabled(context);
+ return true;
+
+ case USER_REPAIR_MODE:
+ if (checkDeviceSupported) return isRepairModeSupported(context);
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Attempt to rederive the unified work challenge for the specified profile user and unlock the
* user. If successful, this would allow the user to leave quiet mode automatically without
* additional user authentication.
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index da24af3..c2cfcd6 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1509,6 +1509,8 @@
int resultColor = ColorUtils.blendARGB(mDotColor, mDotActivatedColor,
/* ratio= */ activationAnimationProgress);
mPaint.setColor(resultColor);
+ } else if (!mFadePattern && partOfPattern){
+ mPaint.setColor(mDotActivatedColor);
} else {
mPaint.setColor(getDotColor());
}
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index fb4b026..e65b4b6 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -359,20 +359,16 @@
// Draw current touch ellipse.
mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
- drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor * mDensity,
- ps.mCoords.touchMinor * mDensity, ps.mCoords.orientation, mPaint);
+ drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
+ ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
// Draw current tool ellipse.
mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
- drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor * mDensity,
- ps.mCoords.toolMinor * mDensity, ps.mCoords.orientation, mPaint);
+ drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
+ ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
- // Draw the orientation arrow.
- float arrowSize = ps.mCoords.toolMajor * 0.7f;
- if (arrowSize < 20) {
- arrowSize = 20;
- }
- arrowSize *= mDensity;
+ // Draw the orientation arrow, and ensure it has a minimum size of 24dp.
+ final float arrowSize = Math.max(ps.mCoords.toolMajor * 0.7f, 24 * mDensity);
mPaint.setARGB(255, pressureLevel, 255, 0);
float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
* arrowSize);
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index da21486..f3acab0 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -13,33 +13,186 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector android:height="128dp"
- android:width="128dp"
- android:viewportHeight="24"
- android:viewportWidth="24"
- xmlns:android="http://schemas.android.com/apk/res/android">
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="512dp"
+ android:height="512dp"
+ android:viewportWidth="512"
+ android:viewportHeight="512">
+ <path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="256"
+ android:startY="21.81"
+ android:endX="256"
+ android:endY="350.42"
+ android:type="linear">
+ <item android:offset="0" android:color="#FF073042"/>
+ <item android:offset="1" android:color="#FF073042"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:fillColor="@android:color/system_accent1_400"
- android:pathData="M11,0.3c0.6,-0.3 1.4,-0.3 2,0l0.6,0.4c0.7,0.4 1.4,0.6 2.2,0.6l0.7,-0.1c0.7,0 1.4,0.3 1.8,0.9l0.3,0.6c0.4,0.7 1,1.2 1.7,1.5L21,4.5c0.7,0.3 1.1,0.9 1.2,1.7v0.7C22.2,7.7 22.5,8.4 23,9l0.4,0.5c0.4,0.6 0.5,1.3 0.2,2l-0.3,0.6c-0.3,0.7 -0.4,1.5 -0.3,2.3l0.1,0.7c0.1,0.7 -0.2,1.4 -0.7,1.9L22,17.5c-0.6,0.5 -1.1,1.1 -1.3,1.9L20.5,20c-0.2,0.7 -0.8,1.2 -1.5,1.4l-0.7,0.1c-0.8,0.2 -1.4,0.5 -2,1.1l-0.5,0.5c-0.5,0.5 -1.3,0.7 -2,0.5l-0.6,-0.2c-0.8,-0.2 -1.5,-0.2 -2.3,0l-0.6,0.2c-0.7,0.2 -1.5,0 -2,-0.5l-0.5,-0.5c-0.5,-0.5 -1.2,-0.9 -2,-1.1L5,21.4c-0.7,-0.2 -1.3,-0.7 -1.5,-1.4l-0.2,-0.7C3.1,18.6 2.6,18 2,17.5l-0.6,-0.4c-0.6,-0.5 -0.8,-1.2 -0.7,-1.9l0.1,-0.7c0.1,-0.8 0,-1.6 -0.3,-2.3l-0.3,-0.6c-0.3,-0.7 -0.2,-1.4 0.2,-2L1,9c0.5,-0.6 0.7,-1.4 0.8,-2.2V6.2C1.9,5.5 2.3,4.8 3,4.5l0.6,-0.3c0.7,-0.3 1.3,-0.9 1.7,-1.5l0.3,-0.6c0.4,-0.6 1.1,-1 1.8,-0.9l0.7,0.1c0.8,0 1.6,-0.2 2.2,-0.6L11,0.3z"
- />
+ android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="M6.3,6.5l3,0l0,12.2"
- android:strokeWidth="2.22"
- android:strokeColor="@android:color/system_accent3_800"
- />
+ android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="M12.3,6.5h4l-2,4c2.2,0.3 3.6,2.4 3.3,4.5c-0.3,1.9 -1.9,3.3 -3.8,3.3c-0.5,0 -1,-0.1 -1.4,-0.3"
- android:strokeWidth="2.22"
- android:strokeColor="@android:color/system_accent3_800"
- />
+ android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="M6.3,6.5l3,0l0,12.2"
- android:strokeWidth="0.56"
- android:strokeColor="@android:color/system_neutral1_100"
- />
+ android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="M12.3,6.5h4l-2,4c2.2,0.3 3.6,2.4 3.3,4.5c-0.3,1.9 -1.9,3.3 -3.8,3.3c-0.5,0 -1,-0.1 -1.4,-0.3"
- android:strokeWidth="0.56"
- android:strokeColor="@android:color/system_neutral1_100"
- />
+ android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
+ android:strokeWidth="56.561"
+ android:fillColor="#00000000"
+ android:strokeColor="#f86734"/>
+ <path
+ android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+ android:fillColor="#fff"/>
</vector>
+
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index cd4c0d6..3e702d3 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -409,6 +409,9 @@
<!-- Tooltip popup properties -->
<item name="tooltipForegroundColor">@color/foreground_material_light</item>
<item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
+
+ <!-- Display cutout properties -->
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
<!-- Material theme (light version). -->
@@ -785,6 +788,9 @@
<!-- Tooltip popup properties -->
<item name="tooltipForegroundColor">@color/foreground_material_dark</item>
<item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
+
+ <!-- Display cutout properties -->
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
<!-- Variant of the material (light) theme that has a solid (opaque) action bar
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 436f058..85d54e0 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -45,6 +45,7 @@
libs: ["android.test.base"],
test_suites: [
"general-tests",
+ "automotive-general-tests",
],
// mockito-target-inline dependency
jni_libs: [
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
index b5f18c2..6ffdee1 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
@@ -45,7 +45,7 @@
@SmallTest
public void testGetEmpty() {
final Resources res = getActivity().getResources();
- assertNull(mCache.getInstance(-1, res, null).getValue());
+ assertNull(mCache.getInstance(-1, res, null));
}
@SmallTest
@@ -53,9 +53,9 @@
mCache.put(1, null, new DummyFloatConstantState(5f),
ThemedResourceCache.UNDEFINED_GENERATION);
final Resources res = getActivity().getResources();
- assertEquals(5f, mCache.getInstance(1, res, null).getValue());
- assertNotSame(5f, mCache.getInstance(1, res, null).getValue());
- assertEquals(false, mCache.getInstance(1, res, getActivity().getTheme()).hasValue());
+ assertEquals(5f, mCache.getInstance(1, res, null));
+ assertNotSame(5f, mCache.getInstance(1, res, null));
+ assertEquals(null, mCache.getInstance(1, res, getActivity().getTheme()));
}
@SmallTest
@@ -63,9 +63,9 @@
mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f),
ThemedResourceCache.UNDEFINED_GENERATION);
final Resources res = getActivity().getResources();
- assertEquals(false, mCache.getInstance(1, res, null).hasValue());
- assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
- assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
+ assertEquals(null, mCache.getInstance(1, res, null));
+ assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
}
@SmallTest
@@ -75,10 +75,10 @@
mCache.put(1, null, new DummyFloatConstantState(10f),
ThemedResourceCache.UNDEFINED_GENERATION);
final Resources res = getActivity().getResources();
- assertEquals(10f, mCache.getInstance(1, res, null).getValue());
- assertNotSame(10f, mCache.getInstance(1, res, null).getValue());
- assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
- assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
+ assertEquals(10f, mCache.getInstance(1, res, null));
+ assertNotSame(10f, mCache.getInstance(1, res, null));
+ assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
}
@SmallTest
@@ -98,9 +98,9 @@
Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
- assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()).getValue());
+ assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
mCache.onConfigurationChange(changes);
- assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()).getValue());
+ assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
}
@SmallTest
@@ -123,9 +123,9 @@
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
assertEquals(changingDim,
- mCache.getInstance(key, res, getActivity().getTheme()).getValue());
+ mCache.getInstance(key, res, getActivity().getTheme()));
mCache.onConfigurationChange(changes);
- assertNull(mCache.get(key, getActivity().getTheme()).getValue());
+ assertNull(mCache.get(key, getActivity().getTheme()));
}
@SmallTest
@@ -152,15 +152,15 @@
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
- getActivity().getTheme()).getValue());
+ getActivity().getTheme()));
assertEquals(changingDim,
mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- getActivity().getTheme()).getValue());
+ getActivity().getTheme()));
mCache.onConfigurationChange(changes);
assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
- getActivity().getTheme()).getValue());
+ getActivity().getTheme()));
assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- getActivity().getTheme()).getValue());
+ getActivity().getTheme()));
}
@SmallTest
@@ -198,18 +198,18 @@
for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
assertEquals(staticDim,
- mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme).getValue());
+ mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
assertEquals(changingDim,
mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- theme).getValue());
+ theme));
}
mCache.onConfigurationChange(changes);
for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
assertEquals(staticDim,
- mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme).getValue());
+ mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- theme).getValue());
+ theme));
}
}
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 89632a4..2a4ca79 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -25,8 +25,6 @@
import java.io.File;
import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -869,84 +867,90 @@
return (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);
}
- /** Attempting to unparcel a legacy parcel format of Uri.{,Path}Part should fail. */
- public void testUnparcelLegacyPart_fails() throws Exception {
- assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$Part"));
- assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$PathPart"));
- }
-
- private static void assertUnparcelLegacyPart_fails(Class partClass) throws Exception {
- Parcel parcel = Parcel.obtain();
- parcel.writeInt(0 /* BOTH */);
- parcel.writeString("encoded");
- parcel.writeString("decoded");
- parcel.setDataPosition(0);
-
- Method readFromMethod = partClass.getDeclaredMethod("readFrom", Parcel.class);
- readFromMethod.setAccessible(true);
- try {
- readFromMethod.invoke(null, parcel);
- fail();
- } catch (InvocationTargetException expected) {
- Throwable targetException = expected.getTargetException();
- // Check that the exception was thrown for the correct reason.
- assertEquals("Unknown representation: 0", targetException.getMessage());
- } finally {
- parcel.recycle();
- }
- }
-
- private Uri buildUriFromRawParcel(boolean argumentsEncoded,
+ private Uri buildUriFromParts(boolean argumentsEncoded,
String scheme,
String authority,
String path,
String query,
String fragment) {
- // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}).
- final int representation = argumentsEncoded ? 1 : 2;
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeInt(3); // hierarchical
- parcel.writeString8(scheme);
- parcel.writeInt(representation);
- parcel.writeString8(authority);
- parcel.writeInt(representation);
- parcel.writeString8(path);
- parcel.writeInt(representation);
- parcel.writeString8(query);
- parcel.writeInt(representation);
- parcel.writeString8(fragment);
- parcel.setDataPosition(0);
- return Uri.CREATOR.createFromParcel(parcel);
- } finally {
- parcel.recycle();
+ final Uri.Builder builder = new Uri.Builder();
+ builder.scheme(scheme);
+ if (argumentsEncoded) {
+ builder.encodedAuthority(authority);
+ builder.encodedPath(path);
+ builder.encodedQuery(query);
+ builder.encodedFragment(fragment);
+ } else {
+ builder.authority(authority);
+ builder.path(path);
+ builder.query(query);
+ builder.fragment(fragment);
}
+ return builder.build();
}
public void testUnparcelMalformedPath() {
// Regression tests for b/171966843.
// Test cases with arguments encoded (covering testing `scheme` * `authority` options).
- Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null);
+ Uri uri0 = buildUriFromParts(true, "https", "google.com", "@evil.com", null, null);
assertEquals("https://google.com/@evil.com", uri0.toString());
- Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x");
+ Uri uri1 = buildUriFromParts(true, null, "google.com", "@evil.com", "name=spark", "x");
assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString());
- Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null);
+ Uri uri2 = buildUriFromParts(true, "http:", null, "@evil.com", null, null);
assertEquals("http::/@evil.com", uri2.toString());
- Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null);
+ Uri uri3 = buildUriFromParts(true, null, null, "@evil.com", null, null);
assertEquals("@evil.com", uri3.toString());
// Test cases with arguments not encoded (covering testing `scheme` * `authority` options).
- Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null);
+ Uri uriA = buildUriFromParts(false, "https", "google.com", "@evil.com", null, null);
assertEquals("https://google.com/%40evil.com", uriA.toString());
- Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null);
+ Uri uriB = buildUriFromParts(false, null, "google.com", "@evil.com", null, null);
assertEquals("//google.com/%40evil.com", uriB.toString());
- Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null);
+ Uri uriC = buildUriFromParts(false, "http:", null, "@evil.com", null, null);
assertEquals("http::/%40evil.com", uriC.toString());
- Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y");
+ Uri uriD = buildUriFromParts(false, null, null, "@evil.com", "name=spark", "y");
assertEquals("%40evil.com?name%3Dspark#y", uriD.toString());
}
+ public void testParsedUriFromStringEquality() {
+ Uri uri = buildUriFromParts(
+ true, "https", "google.com", "@evil.com", null, null);
+ assertEquals(uri, Uri.parse(uri.toString()));
+ Uri uri2 = buildUriFromParts(
+ true, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null);
+ assertEquals(uri2, Uri.parse(uri2.toString()));
+ Uri uri3 = buildUriFromParts(
+ false, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null);
+ assertEquals(uri3, Uri.parse(uri3.toString()));
+ }
+
+ public void testParceledUrisAreEqual() {
+ Uri opaqueUri = Uri.fromParts("fake://uri#", "ssp", "fragment");
+ Parcel parcel = Parcel.obtain();
+ try {
+ opaqueUri.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel);
+ Uri parsedUri = Uri.parse(postParcelUri.toString());
+ assertEquals(parsedUri.getScheme(), postParcelUri.getScheme());
+ } finally {
+ parcel.recycle();
+ }
+
+ Uri hierarchicalUri = new Uri.Builder().scheme("fake://uri#").authority("auth").build();
+ parcel = Parcel.obtain();
+ try {
+ hierarchicalUri.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel);
+ Uri parsedUri = Uri.parse(postParcelUri.toString());
+ assertEquals(parsedUri.getScheme(), postParcelUri.getScheme());
+ } finally {
+ parcel.recycle();
+ }
+ }
+
public void testToSafeString() {
checkToSafeString("tel:xxxxxx", "tel:Google");
checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890");
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java b/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java
index e03b722..481993e5 100644
--- a/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java
+++ b/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java
@@ -59,16 +59,6 @@
assertThat(AutofillFeatureFlags.isCredentialManagerEnabled()).isTrue();
}
- @Test
- public void testShouldIgnoreCredentialManagerViews() {
- setCredentialManagerEnabled(false);
- setIgnoreCredentialManagerViews(true);
- // Overall feature is disabled, so we shouldn't ignore views.
- assertThat(AutofillFeatureFlags.shouldIgnoreCredentialViews()).isFalse();
- setCredentialManagerEnabled(true);
- assertThat(AutofillFeatureFlags.shouldIgnoreCredentialViews()).isTrue();
- }
-
private static void setFillDialogHints(String value) {
setDeviceConfig(
AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS,
@@ -81,12 +71,6 @@
String.valueOf(value));
}
- private static void setIgnoreCredentialManagerViews(boolean value) {
- setDeviceConfig(
- AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS,
- String.valueOf(value));
- }
-
private static void setDeviceConfig(String key, String value) {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_AUTOFILL, key, value, /* makeDefault */ false);
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index f8ebd09..23b9b9b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -32,6 +32,7 @@
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -130,6 +131,7 @@
() -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
}
+ @Ignore("b/286134492")
@Test
public void testNotifyViewsDisappeared_noSendTreeEventBeforeU() {
MyContentCaptureSession session = new MyContentCaptureSession(121);
@@ -139,6 +141,7 @@
assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(0);
}
+ @Ignore("b/286134492")
@EnableCompatChanges({ContentCaptureSession.NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS})
@Test
public void testNotifyViewsDisappeared_sendTreeEventSinceU() {
@@ -151,7 +154,7 @@
@Test
public void testGetFlushReasonAsString() {
- int invalidFlushReason = ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED + 1;
+ int invalidFlushReason = ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED + 1;
Map<Integer, String> expectedMap =
new ImmutableMap.Builder<Integer, String>()
.put(ContentCaptureSession.FLUSH_REASON_FULL, "FULL")
@@ -168,8 +171,7 @@
.put(
ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED,
"VIEW_TREE_APPEARED")
- .put(ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED, "LOGIN_DETECTED")
- .put(invalidFlushReason, "UNKOWN-" + invalidFlushReason)
+ .put(invalidFlushReason, "UNKNOWN-" + invalidFlushReason)
.build();
expectedMap.forEach(
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
new file mode 100644
index 0000000..3373b8b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.contentprotection.ContentProtectionEventProcessor;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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.Arrays;
+import java.util.List;
+
+/**
+ * Test for {@link MainContentCaptureSession}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MainContentCaptureSessionTest {
+
+ private static final int BUFFER_SIZE = 100;
+
+ private static final int REASON = 123;
+
+ private static final ContentCaptureEvent EVENT =
+ new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED);
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.test.package", "TestClass");
+
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+ private static final ContentCaptureManager.StrippedContext sStrippedContext =
+ new ContentCaptureManager.StrippedContext(sContext);
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private IContentCaptureManager mMockSystemServerInterface;
+
+ @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
+
+ @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+ @Test
+ public void onSessionStarted_contentProtectionEnabled_processorCreated() {
+ MainContentCaptureSession session = createSession();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNotNull();
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionDisabled_processorNotCreated() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, -BUFFER_SIZE));
+ MainContentCaptureSession session = createSession(options);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_noComponentName_processorNotCreated() {
+ MainContentCaptureSession session = createSession();
+ session.mComponentName = null;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNotNull();
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() {
+ MainContentCaptureSession session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+ assertThat(session.mEvents).isNotNull();
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void sendEvent_contentProtectionEnabled_processorNotCreated() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+
+ session.sendEvent(EVENT);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void flush_contentCaptureDisabled_contentProtectionEnabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isEmpty();
+ assertEventFlushedContentCapture(options);
+ }
+
+ @Test
+ public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isEmpty();
+ assertEventFlushedContentCapture(options);
+ }
+
+ @Test
+ public void destroySession() throws Exception {
+ MainContentCaptureSession session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.destroySession();
+
+ verify(mMockSystemServerInterface).finishSession(anyInt());
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mDirectServiceInterface).isNull();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ public void resetSession() {
+ MainContentCaptureSession session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.resetSession(/* newState= */ 0);
+
+ verifyZeroInteractions(mMockSystemServerInterface);
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mDirectServiceInterface).isNull();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ private static ContentCaptureOptions createOptions(
+ boolean enableContentCaptureReceiver,
+ ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+ return new ContentCaptureOptions(
+ /* loggingLevel= */ 0,
+ BUFFER_SIZE,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ enableContentCaptureReceiver,
+ contentProtectionOptions,
+ /* whitelistedComponents= */ null);
+ }
+
+ private static ContentCaptureOptions createOptions(
+ boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+ return createOptions(
+ enableContentCaptureReceiver,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ enableContentProtectionReceiver, BUFFER_SIZE));
+ }
+
+ private ContentCaptureManager createManager(ContentCaptureOptions options) {
+ return new ContentCaptureManager(sContext, mMockSystemServerInterface, options);
+ }
+
+ private MainContentCaptureSession createSession(ContentCaptureManager manager) {
+ MainContentCaptureSession session =
+ new MainContentCaptureSession(
+ sStrippedContext,
+ manager,
+ new Handler(Looper.getMainLooper()),
+ mMockSystemServerInterface);
+ session.mComponentName = COMPONENT_NAME;
+ return session;
+ }
+
+ private MainContentCaptureSession createSession(ContentCaptureOptions options) {
+ return createSession(createManager(options));
+ }
+
+ private MainContentCaptureSession createSession(
+ boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+ return createSession(
+ createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver));
+ }
+
+ private MainContentCaptureSession createSession() {
+ return createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ }
+
+ private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception {
+ ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+ verify(mMockContentCaptureDirectManager)
+ .sendEvents(captor.capture(), eq(REASON), eq(options));
+
+ assertThat(captor.getValue()).isNotNull();
+ List<ContentCaptureEvent> actual = captor.getValue().getList();
+ assertThat(actual).isNotNull();
+ assertThat(actual).containsExactly(EVENT);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 6167c4b..1a668f7 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -37,10 +37,23 @@
}
@Test
+ public void testUserRepairMode_isNotRegularUser() {
+ assertTrue(LockPatternUtils.USER_REPAIR_MODE < 0);
+ }
+
+ @Test
public void testUserFrp_isNotAReservedSpecialUser() throws Exception {
assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_FRP);
assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_FRP);
assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_FRP);
assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_FRP);
}
+
+ @Test
+ public void testUserRepairMode_isNotAReservedSpecialUser() throws Exception {
+ assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_REPAIR_MODE);
+ assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_REPAIR_MODE);
+ assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
+ assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
+ }
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 2307d60..b9d3756 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -997,12 +997,63 @@
canvas.concat(m);
canvas.drawBitmap(source, srcR, dstR, paint);
canvas.setBitmap(null);
+
+ // If the source has a gainmap, apply the same set of transformations to the gainmap
+ // and set it on the output
+ if (source.hasGainmap()) {
+ Bitmap newMapContents = transformGainmap(source, m, neww, newh, paint, srcR, dstR,
+ deviceR);
+ if (newMapContents != null) {
+ bitmap.setGainmap(new Gainmap(source.getGainmap(), newMapContents));
+ }
+ }
+
if (isHardware) {
return bitmap.copy(Config.HARDWARE, false);
}
return bitmap;
}
+ private static Bitmap transformGainmap(Bitmap source, Matrix m, int neww, int newh, Paint paint,
+ Rect srcR, RectF dstR, RectF deviceR) {
+ Canvas canvas;
+ Bitmap sourceGainmap = source.getGainmap().getGainmapContents();
+ // Gainmaps can be scaled relative to the base image (eg, 1/4th res)
+ // Preserve that relative scaling between the base & gainmap in the output
+ float scaleX = (sourceGainmap.getWidth() / (float) source.getWidth());
+ float scaleY = (sourceGainmap.getHeight() / (float) source.getHeight());
+ int mapw = Math.round(neww * scaleX);
+ int maph = Math.round(newh * scaleY);
+
+ if (mapw == 0 || maph == 0) {
+ // The gainmap has been scaled away entirely, drop it
+ return null;
+ }
+
+ // Scale the computed `srcR` used for rendering the source bitmap to the destination
+ // to be in gainmap dimensions
+ Rect gSrcR = new Rect((int) (srcR.left * scaleX),
+ (int) (srcR.top * scaleY), (int) (srcR.right * scaleX),
+ (int) (srcR.bottom * scaleY));
+
+ // Note: createBitmap isn't used as that requires a non-null colorspace, however
+ // gainmaps don't have a colorspace. So use `nativeCreate` directly to bypass
+ // that colorspace enforcement requirement (#getColorSpace() allows a null return)
+ Bitmap newMapContents = nativeCreate(null, 0, mapw, mapw, maph,
+ sourceGainmap.getConfig().nativeInt, true, 0);
+ newMapContents.eraseColor(0);
+ canvas = new Canvas(newMapContents);
+ // Scale the translate & matrix to be in gainmap-relative dimensions
+ canvas.scale(scaleX, scaleY);
+ canvas.translate(-deviceR.left, -deviceR.top);
+ canvas.concat(m);
+ canvas.drawBitmap(sourceGainmap, gSrcR, dstR, paint);
+ canvas.setBitmap(null);
+ // Create a new gainmap using a copy of the metadata information from the source but
+ // with the transformed bitmap created above
+ return newMapContents;
+ }
+
/**
* Returns a mutable bitmap with the specified width and height. Its
* initial density is as per {@link #getDensity}. The newly created
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 9ac84a6..f639521 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -122,6 +122,16 @@
}
/**
+ * Creates a new gainmap using the provided gainmap as the metadata source and the provided
+ * bitmap as the replacement for the gainmapContents
+ * TODO: Make public, it's useful
+ * @hide
+ */
+ public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
+ this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
+ }
+
+ /**
* @return Returns the image data of the gainmap represented as a Bitmap. This is represented
* as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored
* such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not
@@ -325,6 +335,7 @@
private static native long nGetFinalizer();
private static native long nCreateEmpty();
+ private static native long nCreateCopy(long source);
private static native void nSetBitmap(long ptr, Bitmap bitmap);
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 8635c56..d902fd4 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -93,62 +93,34 @@
<style name="RestartDialogTitleText">
<item name="android:textSize">24sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:lineSpacingExtra">2sp</item>
- <item name="android:textAppearance">
- @*android:style/TextAppearance.DeviceDefault.Headline
- </item>
- <item name="android:fontFamily">
- @*android:string/config_bodyFontFamilyMedium
- </item>
+ <item name="android:lineSpacingExtra">8sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
</style>
- <style name="RestartDialogBodyText">
+ <style name="RestartDialogBodyStyle">
<item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ </style>
+
+ <style name="RestartDialogBodyText" parent="RestartDialogBodyStyle">
<item name="android:letterSpacing">0.02</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
- <item name="android:lineSpacingExtra">2sp</item>
- <item name="android:textAppearance">
- @*android:style/TextAppearance.DeviceDefault.Body2
- </item>
- <item name="android:fontFamily">
- @*android:string/config_bodyFontFamily
- </item>
+ <item name="android:lineSpacingExtra">6sp</item>
</style>
- <style name="RestartDialogCheckboxText">
- <item name="android:textSize">16sp</item>
+ <style name="RestartDialogCheckboxText" parent="RestartDialogBodyStyle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:lineSpacingExtra">4sp</item>
- <item name="android:textAppearance">
- @*android:style/TextAppearance.DeviceDefault.Headline
- </item>
- <item name="android:fontFamily">
- @*android:string/config_bodyFontFamilyMedium
- </item>
+ <item name="android:lineSpacingExtra">6sp</item>
</style>
- <style name="RestartDialogDismissButton">
+ <style name="RestartDialogDismissButton" parent="RestartDialogBodyStyle">
<item name="android:lineSpacingExtra">2sp</item>
- <item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:textAppearance">
- @*android:style/TextAppearance.DeviceDefault.Body2
- </item>
- <item name="android:fontFamily">
- @*android:string/config_bodyFontFamily
- </item>
</style>
- <style name="RestartDialogConfirmButton">
+ <style name="RestartDialogConfirmButton" parent="RestartDialogBodyStyle">
<item name="android:lineSpacingExtra">2sp</item>
- <item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
- <item name="android:textAppearance">
- @*android:style/TextAppearance.DeviceDefault.Body2
- </item>
- <item name="android:fontFamily">
- @*android:string/config_bodyFontFamily
- </item>
</style>
<style name="ReachabilityEduHandLayout" parent="Theme.AppCompat.Light">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index d6e1a82..467e9c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -375,16 +375,15 @@
insetsState.getDisplayFrame(),
WindowInsets.Type.navigationBars(),
false /* ignoreVisibility */);
- outInsets.set(insets.left, insets.top, insets.right, insets.bottom);
int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
int navBarSize =
getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
if (position == NAV_BAR_BOTTOM) {
- outInsets.bottom = Math.max(outInsets.bottom , navBarSize);
+ outInsets.bottom = Math.max(insets.bottom , navBarSize);
} else if (position == NAV_BAR_RIGHT) {
- outInsets.right = Math.max(outInsets.right , navBarSize);
+ outInsets.right = Math.max(insets.right , navBarSize);
} else if (position == NAV_BAR_LEFT) {
- outInsets.left = Math.max(outInsets.left , navBarSize);
+ outInsets.left = Math.max(insets.left , navBarSize);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 70e9079..ec40244 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,6 +57,7 @@
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -690,6 +691,7 @@
Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
+ ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor
@@ -697,7 +699,8 @@
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- desktopModeTaskRepository, launchAdjacentController, mainExecutor);
+ toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
+ launchAdjacentController, mainExecutor);
}
@WMSingleton
@@ -709,6 +712,13 @@
@WMSingleton
@Provides
+ static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
+ Transitions transitions) {
+ return new ToggleResizeDesktopTaskTransitionHandler(transitions);
+ }
+
+ @WMSingleton
+ @Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
Context context
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index efbd52f..d016e73c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -31,6 +31,7 @@
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
+import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -38,7 +39,6 @@
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -79,6 +79,8 @@
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+ private val toggleResizeDesktopTaskTransitionHandler:
+ ToggleResizeDesktopTaskTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
@ShellMainThread private val mainExecutor: ShellExecutor
@@ -184,7 +186,7 @@
)
// Bring other apps to front first
bringDesktopAppsToFront(task.displayId, wct)
- addMoveToDesktopChanges(wct, task.token)
+ addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
@@ -205,7 +207,7 @@
)
val wct = WindowContainerTransaction()
moveHomeTaskToFront(wct)
- addMoveToDesktopChanges(wct, taskInfo.getToken())
+ addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -225,7 +227,7 @@
)
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(taskInfo.displayId, wct)
- addMoveToDesktopChanges(wct, taskInfo.getToken())
+ addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -251,7 +253,7 @@
)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task.token)
+ addMoveToFullscreenChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -270,7 +272,7 @@
task.taskId
)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task.token)
+ addMoveToFullscreenChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position,
mOnAnimationFinishedCallback)
@@ -287,7 +289,7 @@
task.taskId
)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task.token)
+ addMoveToFullscreenChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
exitDesktopTaskTransitionHandler.startTransition(
@@ -383,6 +385,49 @@
}
}
+ /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
+ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ val destinationBounds = Rect()
+ if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
+ // The desktop task is currently occupying the whole stable bounds, toggle to the
+ // default bounds.
+ getDefaultDesktopTaskBounds(
+ density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
+ stableBounds = stableBounds,
+ outBounds = destinationBounds
+ )
+ } else {
+ // Toggle to the stable bounds.
+ destinationBounds.set(stableBounds)
+ }
+
+ val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ toggleResizeDesktopTaskTransitionHandler.startTransition(
+ wct,
+ taskInfo.taskId,
+ windowDecor
+ )
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
+ val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
+ val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
+ outBounds.set(0, 0, width, height)
+ // Center the task in stable bounds
+ outBounds.offset(
+ stableBounds.centerX() - outBounds.centerX(),
+ stableBounds.centerY() - outBounds.centerY()
+ )
+ }
+
/**
* Get windowing move for a given `taskId`
*
@@ -516,7 +561,7 @@
task.taskId
)
return WindowContainerTransaction().also { wct ->
- addMoveToFullscreenChanges(wct, task.token)
+ addMoveToFullscreenChanges(wct, task)
}
}
return null
@@ -532,7 +577,7 @@
task.taskId
)
return WindowContainerTransaction().also { wct ->
- addMoveToDesktopChanges(wct, task.token)
+ addMoveToDesktopChanges(wct, task)
}
}
return null
@@ -546,30 +591,44 @@
)
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(task.displayId, wct)
- addMoveToDesktopChanges(wct, task.token)
+ addMoveToDesktopChanges(wct, task)
desktopModeTaskRepository.setStashed(task.displayId, false)
return wct
}
private fun addMoveToDesktopChanges(
wct: WindowContainerTransaction,
- token: WindowContainerToken
+ taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
- wct.reorder(token, true /* onTop */)
+ val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+ val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ // Display windowing is freeform, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FREEFORM
+ }
+ wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+ wct.reorder(taskInfo.token, true /* onTop */)
if (isDesktopDensityOverrideSet()) {
- wct.setDensityDpi(token, getDesktopDensityDpi())
+ wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi())
}
}
private fun addMoveToFullscreenChanges(
wct: WindowContainerTransaction,
- token: WindowContainerToken
+ taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
- wct.setBounds(token, null)
+ val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+ val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // Display windowing is fullscreen, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FULLSCREEN
+ }
+ wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+ wct.setBounds(taskInfo.token, null)
if (isDesktopDensityOverrideSet()) {
- wct.setDensityDpi(token, getFullscreenDensityDpi())
+ wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi())
}
}
@@ -870,6 +929,14 @@
SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+ // Override default freeform task width when desktop mode is enabled. In dips.
+ private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
+
+ // Override default freeform task height when desktop mode is enabled. In dips.
+ private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
+
/**
* Check if desktop density override is enabled
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
new file mode 100644
index 0000000..94788e4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.core.animation.addListener
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import java.util.function.Supplier
+
+/** Handles the animation of quick resizing of desktop tasks. */
+class ToggleResizeDesktopTaskTransitionHandler(
+ private val transitions: Transitions,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+) : Transitions.TransitionHandler {
+
+ private val rectEvaluator = RectEvaluator(Rect())
+ private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+
+ private var boundsAnimator: Animator? = null
+
+ constructor(
+ transitions: Transitions
+ ) : this(transitions, Supplier { SurfaceControl.Transaction() })
+
+ /** Starts a quick resize transition. */
+ fun startTransition(
+ wct: WindowContainerTransaction,
+ taskId: Int,
+ windowDecoration: DesktopModeWindowDecoration
+ ) {
+ // Pause relayout until the transition animation finishes.
+ windowDecoration.incrementRelayoutBlock()
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
+ taskToDecorationMap.put(taskId, windowDecoration)
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ): Boolean {
+ val change = findRelevantChange(info)
+ val leash = change.leash
+ val taskId = change.taskInfo.taskId
+ val startBounds = change.startAbsBounds
+ val endBounds = change.endAbsBounds
+ val windowDecor =
+ taskToDecorationMap.removeReturnOld(taskId)
+ ?: throw IllegalStateException("Window decoration not found for task $taskId")
+
+ val tx = transactionSupplier.get()
+ boundsAnimator?.cancel()
+ boundsAnimator =
+ ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds)
+ .setDuration(RESIZE_DURATION_MS)
+ .apply {
+ addListener(
+ onStart = {
+ startTransaction
+ .setPosition(
+ leash,
+ startBounds.left.toFloat(),
+ startBounds.top.toFloat()
+ )
+ .setWindowCrop(leash, startBounds.width(), startBounds.height())
+ .show(leash)
+ windowDecor.showResizeVeil(startTransaction, startBounds)
+ },
+ onEnd = {
+ finishTransaction
+ .setPosition(
+ leash,
+ endBounds.left.toFloat(),
+ endBounds.top.toFloat()
+ )
+ .setWindowCrop(leash, endBounds.width(), endBounds.height())
+ .show(leash)
+ windowDecor.hideResizeVeil()
+ finishCallback.onTransitionFinished(null, null)
+ boundsAnimator = null
+ }
+ )
+ addUpdateListener { anim ->
+ val rect = anim.animatedValue as Rect
+ tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(leash, rect.width(), rect.height())
+ .show(leash)
+ windowDecor.updateResizeVeil(tx, rect)
+ }
+ start()
+ }
+ return true
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ return null
+ }
+
+ private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
+ val matchingChanges =
+ info.changes.filter { c ->
+ !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE
+ }
+ if (matchingChanges.size != 1) {
+ throw IllegalStateException(
+ "Expected 1 relevant change but found: ${matchingChanges.size}"
+ )
+ }
+ return matchingChanges.first()
+ }
+
+ private fun isWallpaper(change: TransitionInfo.Change): Boolean {
+ return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0
+ }
+
+ private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
+ return change.taskInfo != null && change.taskInfo?.taskId != -1
+ }
+
+ companion object {
+ private const val RESIZE_DURATION_MS = 300L
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index d6824fd..fb966c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -816,6 +816,12 @@
final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
final SurfaceControl leash = pipChange.getLeash();
final int startRotation = pipChange.getStartRotation();
+ // Check again in case some callers use startEnterAnimation directly so the flag was not
+ // set in startAnimation, e.g. from DefaultMixedHandler.
+ if (!mInFixedRotation) {
+ mEndFixedRotation = pipChange.getEndFixedRotation();
+ mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+ }
final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
@@ -844,7 +850,7 @@
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
- sourceHintRect, destinationBounds, rotationDelta, taskInfo);
+ sourceHintRect, destinationBounds, taskInfo);
return;
}
@@ -935,8 +941,15 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
- @NonNull Rect destinationBounds, int rotationDelta,
+ @NonNull Rect destinationBounds,
@NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
+ if (mInFixedRotation) {
+ // If rotation changes when returning to home, the transition should contain both the
+ // entering PiP and the display change (PipController#startSwipePipToHome has updated
+ // the display layout to new rotation). So it is not expected to see fixed rotation.
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
+ }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -947,12 +960,7 @@
mPipOrganizer.mSwipePipToHomeOverlay = null;
}
- Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
- if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) {
- // PipController#startSwipePipToHome has updated the display layout to new rotation,
- // so flip the source bounds to match the same orientation.
- sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width());
- }
+ final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index e7a1395..5e1b6be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -31,6 +31,8 @@
import android.util.Size;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import com.android.internal.protolog.common.ProtoLog;
@@ -131,6 +133,8 @@
private PipMenuView mPipMenuView;
+ private SurfaceControl mLeash;
+
private ActionListener mMediaActionListener = new ActionListener() {
@Override
public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
@@ -166,6 +170,7 @@
*/
@Override
public void attach(SurfaceControl leash) {
+ mLeash = leash;
attachPipMenuView();
}
@@ -176,6 +181,7 @@
public void detach() {
hideMenu();
detachPipMenuView();
+ mLeash = null;
}
void attachPipMenuView() {
@@ -185,6 +191,36 @@
}
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
mSplitScreenController, mPipUiEventLogger);
+ mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new ViewRootImpl.SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ final SurfaceControl sc = getSurfaceControl();
+ if (sc != null) {
+ t.reparent(sc, mLeash);
+ // make menu on top of the surface
+ t.setLayer(sc, Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ }
+ });
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
@@ -321,30 +357,10 @@
return;
}
- // If there is no pip leash supplied, that means the PiP leash is already finalized
- // resizing and the PiP menu is also resized. We then want to do a scale from the current
- // new menu bounds.
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
if (pipLeash != null && t != null) {
- mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
- } else {
- mTmpSourceBounds.set(0, 0, destinationBounds.width(), destinationBounds.height());
+ t.apply();
}
-
- mTmpSourceRectF.set(mTmpSourceBounds);
- mTmpDestinationRectF.set(destinationBounds);
- mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- final SurfaceControl surfaceControl = getSurfaceControl();
- if (surfaceControl == null) {
- return;
- }
- final SurfaceControl.Transaction menuTx =
- mSurfaceControlTransactionFactory.getTransaction();
- menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
- if (pipLeash != null && t != null) {
- // Merge the two transactions, vsyncId has been set on menuTx.
- menuTx.merge(t);
- }
- menuTx.apply();
}
/**
@@ -362,18 +378,10 @@
return;
}
- final SurfaceControl surfaceControl = getSurfaceControl();
- if (surfaceControl == null) {
- return;
- }
- final SurfaceControl.Transaction menuTx =
- mSurfaceControlTransactionFactory.getTransaction();
- menuTx.setCrop(surfaceControl, destinationBounds);
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
if (pipLeash != null && t != null) {
- // Merge the two transactions, vsyncId has been set on menuTx.
- menuTx.merge(t);
+ t.apply();
}
- menuTx.apply();
}
private boolean checkPipMenuState() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 8723f9b..a612f5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -595,9 +595,20 @@
cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
return;
}
- // Don't consider order-only changes as changing apps.
- if (!TransitionUtil.isOrderOnly(change)) {
+ // Don't consider order-only & non-leaf changes as changing apps.
+ if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
+ } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
+ && !mRecentsTask.equals(change.getContainer())) {
+ // Unless it is a 3p launcher. This means that the 3p launcher was already
+ // visible (eg. the "pausing" task is translucent over the 3p launcher).
+ // Treat it as if we are "re-opening" the 3p launcher.
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ openingTaskIsLeafs = new IntArray();
+ }
+ openingTasks.add(change);
+ openingTaskIsLeafs.add(1);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 9863099..d21f8a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -199,19 +199,7 @@
boolean isOpening = TransitionUtil.isOpeningType(info.getType());
if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
// fade out
- if (change.getSnapshot() != null) {
- // This case is happened if task is going to reparent to TDA, the origin leash
- // doesn't rendor so we use snapshot to replace it animating.
- t.reparent(change.getSnapshot(), info.getRoot(rootIdx).getLeash());
- // Use origin leash layer.
- t.setLayer(change.getSnapshot(), info.getChanges().size() - i);
- t.setPosition(change.getSnapshot(), change.getStartAbsBounds().left,
- change.getStartAbsBounds().top);
- t.show(change.getSnapshot());
- startFadeAnimation(change.getSnapshot(), false /* show */);
- } else {
- startFadeAnimation(leash, false /* show */);
- }
+ startFadeAnimation(leash, false /* show */);
} else if (mode == TRANSIT_CHANGE && change.getSnapshot() != null) {
t.reparent(change.getSnapshot(), info.getRoot(rootIdx).getLeash());
// Ensure snapshot it on the top of all transition surfaces
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 57111b9..3eff856 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
@@ -1654,7 +1654,7 @@
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
- if (present && visible) {
+ if (present) {
updateRecentTasksSplitPair();
}
@@ -2522,6 +2522,7 @@
if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
startTransaction, finishTransaction, finishCallback)) {
mSplitLayout.update(startTransaction);
+ startTransaction.apply();
return true;
}
}
@@ -2906,6 +2907,12 @@
/** Call this when the recents animation canceled during split-screen. */
public void onRecentsInSplitAnimationCanceled() {
mPausingTasks.clear();
+ setSplitsVisible(false);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ true /* reparentLeafTaskIfRelaunch */);
+ mTaskOrganizer.applyTransaction(wct);
}
/** Call this when the recents animation during split-screen finishes. */
@@ -2932,7 +2939,6 @@
setSplitsVisible(false);
finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* reparentLeafTaskIfRelaunch */);
- logExit(EXIT_REASON_UNKNOWN);
}
/** Call this when the recents animation finishes by doing pair-to-pair switch. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index e3ed5dd..3ef4f02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -132,7 +132,8 @@
* Returns the top visible child task's id.
*/
int getTopVisibleChildTaskId() {
- final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible);
+ final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible
+ && t.isVisibleRequested);
return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID;
}
@@ -188,7 +189,8 @@
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
mChildrenTaskInfo.put(taskId, taskInfo);
- mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */,
+ taskInfo.isVisible && taskInfo.isVisibleRequested);
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
@@ -229,7 +231,7 @@
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
- taskInfo.isVisible);
+ taskInfo.isVisible && taskInfo.isVisibleRequested);
if (!ENABLE_SHELL_TRANSITIONS) {
updateChildTaskSurface(
taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 1bbd367..163cf50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -61,6 +61,16 @@
private TaskViewBase mTaskViewBase;
private final Context mContext;
+ /**
+ * There could be a situation where we have task info and receive
+ * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}, however, the
+ * activity might fail to open, and in this case we need to clean up the task view / notify
+ * listeners of a task removal. This requires task info, so we save the info from onTaskAppeared
+ * in this situation to allow us to notify listeners correctly if the task failed to open.
+ */
+ private ActivityManager.RunningTaskInfo mPendingInfo;
+ /* Indicates that the task we attempted to launch in the task view failed to launch. */
+ private boolean mTaskNotFound;
protected ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
private SurfaceControl mTaskLeash;
@@ -236,6 +246,8 @@
mTaskInfo = null;
mTaskToken = null;
mTaskLeash = null;
+ mPendingInfo = null;
+ mTaskNotFound = false;
}
private void updateTaskVisibility() {
@@ -257,6 +269,12 @@
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
if (isUsingShellTransitions()) {
+ mPendingInfo = taskInfo;
+ if (mTaskNotFound) {
+ // If we were already notified by shell transit that we don't have the
+ // the task, clean it up now.
+ cleanUpPendingTask();
+ }
// Everything else handled by enter transition.
return;
}
@@ -455,6 +473,42 @@
return mTaskInfo;
}
+ /**
+ * Indicates that the task was not found in the start animation for the transition.
+ * In this case we should clean up the task if we have the pending info. If we don't
+ * have the pending info, we'll do it when we receive it in
+ * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}.
+ */
+ void setTaskNotFound() {
+ mTaskNotFound = true;
+ if (mPendingInfo != null) {
+ cleanUpPendingTask();
+ }
+ }
+
+ /**
+ * Called when a task failed to open and we need to clean up task view /
+ * notify users of task view.
+ */
+ void cleanUpPendingTask() {
+ if (mPendingInfo != null) {
+ if (mListener != null) {
+ final int taskId = mPendingInfo.taskId;
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskRemovalStarted(taskId);
+ });
+ }
+ mTaskViewBase.onTaskVanished(mPendingInfo);
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false);
+
+ // Make sure the task is removed
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(mPendingInfo.token);
+ mTaskViewTransitions.closeTaskView(wct, this);
+ }
+ resetTaskInfo();
+ }
+
void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
if (mTaskToken == null) {
// Nothing to update, task is not yet available
@@ -492,6 +546,7 @@
@NonNull SurfaceControl.Transaction finishTransaction,
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
WindowContainerTransaction wct) {
+ mPendingInfo = null;
mTaskInfo = taskInfo;
mTaskToken = mTaskInfo.token;
mTaskLeash = leash;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index fe2faaf..944acb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -135,6 +135,22 @@
}
/**
+ * Looks through the pending transitions for a opening transaction that matches the provided
+ * `taskView`.
+ * @param taskView the pending transition should be for this.
+ */
+ @VisibleForTesting
+ PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) {
+ for (int i = mPending.size() - 1; i >= 0; --i) {
+ if (mPending.get(i).mTaskView != taskView) continue;
+ if (TransitionUtil.isOpeningType(mPending.get(i).mType)) {
+ return mPending.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
* @param type the type of transition it's looking for
@@ -369,10 +385,11 @@
}
}
if (stillNeedsMatchingLaunch) {
- throw new IllegalStateException("Expected a TaskView launch in this transition but"
- + " didn't get one.");
- }
- if (wct == null && pending == null && changesHandled != info.getChanges().size()) {
+ Slog.w(TAG, "Expected a TaskView launch in this transition but didn't get one, "
+ + "cleaning up the task view");
+ // Didn't find a task so the task must have never launched
+ pending.mTaskView.setTaskNotFound();
+ } else if (wct == null && pending == null && changesHandled != info.getChanges().size()) {
// Just some house-keeping, let another handler animate.
return false;
}
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 cdc82ea..e42c662 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
@@ -161,6 +161,10 @@
public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+ /** Transition type to animate the toggle resize between the max and default desktop sizes. */
+ public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 14;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b4a1274..92c2a7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -33,6 +33,8 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -258,7 +260,7 @@
* @return {@code true} if a drag is happening; or {@code false} if it is not
*/
@Override
- public boolean handleMotionEvent(MotionEvent e) {
+ public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 4e61aaf..c93a11d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -43,6 +43,7 @@
import android.os.Looper;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.GestureDetector;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
@@ -198,7 +199,8 @@
if (change.getMode() == WindowManager.TRANSIT_CHANGE
&& (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE)) {
+ || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+ || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition);
}
@@ -279,15 +281,17 @@
}
}
- private class DesktopModeTouchEventListener implements
- View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+ private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
+ implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
+ private final GestureDetector mGestureDetector;
private boolean mIsDragging;
+ private boolean mShouldClick;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
@@ -297,6 +301,7 @@
mTaskToken = taskInfo.token;
mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
+ mGestureDetector = new GestureDetector(mContext, this);
}
@Override
@@ -357,7 +362,7 @@
return false;
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
- return mDragDetector.onMotionEvent(e);
+ return mDragDetector.onMotionEvent(v, e);
}
private void moveTaskToFront(RunningTaskInfo taskInfo) {
@@ -372,7 +377,7 @@
* @return {@code true} if the motion event is handled.
*/
@Override
- public boolean handleMotionEvent(MotionEvent e) {
+ public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -383,6 +388,9 @@
== WINDOWING_MODE_FULLSCREEN) {
return false;
}
+ if (mGestureDetector.onTouchEvent(e)) {
+ return true;
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
@@ -390,7 +398,8 @@
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
mIsDragging = false;
- return false;
+ mShouldClick = true;
+ return true;
}
case MotionEvent.ACTION_MOVE: {
final DesktopModeWindowDecoration decoration =
@@ -404,10 +413,20 @@
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mIsDragging = true;
+ mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ final boolean wasDragging = mIsDragging;
+ if (!wasDragging) {
+ if (mShouldClick && v != null) {
+ v.performClick();
+ mShouldClick = false;
+ return true;
+ }
+ return false;
+ }
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
@@ -422,13 +441,21 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
- final boolean wasDragging = mIsDragging;
mIsDragging = false;
- return wasDragging;
+ return true;
}
}
return true;
}
+
+ @Override
+ public boolean onDoubleTap(@NonNull MotionEvent e) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ mDesktopTasksController.ifPresent(c -> {
+ c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
+ });
+ return true;
+ }
}
// InputEventReceiver to listen for touch input outside of caption bounds
@@ -813,6 +840,7 @@
mMainChoreographer,
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+ windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
windowDecoration, taskInfo);
@@ -843,7 +871,6 @@
mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
mTransactionFactory);
} else {
- windowDecoration.createResizeVeil();
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
mTransitions);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5cb7d48..3ccb938 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -294,23 +294,37 @@
}
/**
- * Fade in the resize veil
+ * Show the resize veil.
*/
- void showResizeVeil(Rect taskBounds) {
+ public void showResizeVeil(Rect taskBounds) {
mResizeVeil.showVeil(mTaskSurface, taskBounds);
}
/**
+ * Show the resize veil.
+ */
+ public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
+ mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
+ }
+
+ /**
* Set new bounds for the resize veil
*/
- void updateResizeVeil(Rect newBounds) {
+ public void updateResizeVeil(Rect newBounds) {
mResizeVeil.updateResizeVeil(newBounds);
}
/**
+ * Set new bounds for the resize veil
+ */
+ public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
+ mResizeVeil.updateResizeVeil(tx, newBounds);
+ }
+
+ /**
* Fade the resize veil out.
*/
- void hideResizeVeil() {
+ public void hideResizeVeil() {
mResizeVeil.hideVeil();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 58644b2..da26898 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -24,6 +24,9 @@
import android.graphics.PointF;
import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
/**
* A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
@@ -54,14 +57,24 @@
*
* @return the result returned by {@link #mEventHandler}, or the result when
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
- */
+ */
boolean onMotionEvent(MotionEvent ev) {
+ return onMotionEvent(null /* view */, ev);
+ }
+
+ /**
+ * The receiver of the {@link MotionEvent} flow.
+ *
+ * @return the result returned by {@link #mEventHandler}, or the result when
+ * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+ */
+ boolean onMotionEvent(View v, MotionEvent ev) {
final boolean isTouchScreen =
(ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
if (!isTouchScreen) {
// Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
// to take the slop threshold into consideration.
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
}
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
@@ -69,7 +82,7 @@
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY);
- mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+ mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev);
return mResultOfDownAction;
}
case ACTION_MOVE: {
@@ -87,7 +100,7 @@
// The event handler should only be notified about 'move' events if a drag has been
// detected.
if (mIsDragEvent) {
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
} else {
return mResultOfDownAction;
}
@@ -95,10 +108,10 @@
case ACTION_UP:
case ACTION_CANCEL: {
resetState();
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
}
default:
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
}
}
@@ -114,6 +127,6 @@
}
interface MotionEventHandler {
- boolean handleMotionEvent(MotionEvent ev);
+ boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 287d861..05a7588 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -42,6 +42,7 @@
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
@@ -303,7 +304,7 @@
}
@Override
- public boolean handleMotionEvent(MotionEvent e) {
+ public boolean handleMotionEvent(View v, MotionEvent e) {
boolean result = false;
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 8277109..bfce72b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -102,11 +102,17 @@
}
/**
- * Animate veil's alpha to 1, fading it in.
+ * Shows the veil surface/view.
+ *
+ * @param t the transaction to apply in sync with the veil draw
+ * @param parentSurface the surface that the veil should be a child of
+ * @param taskBounds the bounds of the task that owns the veil
+ * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
+ * immediately
*/
- public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
+ Rect taskBounds, boolean fadeIn) {
// Parent surface can change, ensure it is up to date.
- SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
if (!parentSurface.equals(mParentSurface)) {
t.reparent(mVeilSurface, parentSurface);
mParentSurface = parentSurface;
@@ -115,22 +121,36 @@
int backgroundColorId = getBackgroundColorId();
mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(0f, 1f);
- animator.setDuration(RESIZE_ALPHA_DURATION);
- animator.addUpdateListener(animation -> {
- t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
- t.apply();
- });
-
relayout(taskBounds, t);
- t.show(mVeilSurface)
- .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start())
- .setAlpha(mVeilSurface, 0);
+ if (fadeIn) {
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(RESIZE_ALPHA_DURATION);
+ animator.addUpdateListener(animation -> {
+ t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+ t.apply();
+ });
+
+ t.show(mVeilSurface)
+ .addTransactionCommittedListener(
+ mContext.getMainExecutor(), () -> animator.start())
+ .setAlpha(mVeilSurface, 0);
+ } else {
+ // Show the veil immediately at full opacity.
+ t.show(mVeilSurface).setAlpha(mVeilSurface, 1);
+ }
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
}
/**
+ * Animate veil's alpha to 1, fading it in.
+ */
+ public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
+ }
+
+ /**
* Update veil bounds to match bounds changes.
* @param newBounds bounds to update veil to.
*/
@@ -147,6 +167,16 @@
*/
public void updateResizeVeil(Rect newBounds) {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ updateResizeVeil(t, newBounds);
+ }
+
+ /**
+ * Calls relayout to update task and veil bounds.
+ *
+ * @param t a transaction to be applied in sync with the veil draw.
+ * @param newBounds bounds to update veil to.
+ */
+ public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
relayout(newBounds, t);
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ee407c0..4407f2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -267,6 +267,7 @@
.setColor(mTaskSurface, mTmpColor)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setShadowRadius(mTaskSurface, shadowRadius)
.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
new file mode 100644
index 0000000..f7ce870
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home from split screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in full screen
+ * Select "Auto-enter PiP" radio button
+ * Open all apps and drag another app icon to enter split screen
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. All assertions are inherited from [EnterPipTest]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
+ AutoEnterPipOnGoToHomeTest(flicker) {
+ private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+ /** Second app used to enter split screen mode */
+ protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
+ fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
+
+ /** Defines the transition used to run the test */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ secondAppForSplitScreen.launchViaIntent(wmHelper)
+ pipApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ enterSplitScreen()
+ // wait until split screen is established
+ wmHelper
+ .StateSyncBuilder()
+ .withWindowSurfaceAppeared(pipApp)
+ .withWindowSurfaceAppeared(secondAppForSplitScreen)
+ .withSplitDividerVisible()
+ .waitForAndVerify()
+ pipApp.enableAutoEnterForPipActivity()
+ }
+ teardown {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
+ secondAppForSplitScreen.exit(wmHelper)
+ }
+ transitions { tapl.goHome() }
+ }
+
+ // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
+ private val TIMEOUT_MS = 3_000L
+ private val overviewSnapshotSelector: BySelector
+ get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
+ private fun enterSplitScreen() {
+ // Note: The initial split position in landscape is different between tablet and phone.
+ // In landscape, tablet will let the first app split to right side, and phone will
+ // split to left side.
+ if (tapl.isTablet) {
+ // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+ // contains more than 3 task views. We need to use uiautomator directly to find the
+ // second task to split.
+ tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector),
+ TIMEOUT_MS)
+ if (snapshots == null || snapshots.size < 1) {
+ error("Fail to find a overview snapshot to split.")
+ }
+
+ // Find the second task in the upper right corner in split select mode by sorting
+ // 'left' in descending order and 'top' in ascending order.
+ snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+ t2.getVisibleBounds().left - t1.getVisibleBounds().left
+ }
+ snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
+ snapshots[0].click()
+ } else {
+ tapl.workspace
+ .switchToOverview()
+ .currentTask
+ .tapMenu()
+ .tapSplitMenuItem()
+ .currentTask
+ .open()
+ }
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // when entering from split screen we use alpha animation, without overlay
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // when entering from split screen we use alpha animation, without overlay
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerReduces() {
+ // when entering from split screen we use alpha animation, so there is no size change
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipLayerReduces()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ // pip layer in gesture nav will disappear during transition with alpha animation
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppLayerAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipWindowRemainInsideVisibleBounds() {
+ if (tapl.isTablet) {
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ } else {
+ // on phones home does not rotate in landscape, PiP enters back to portrait
+ // orientation so use display bounds from that orientation for assertion
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(portraitDisplayBounds) }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index ef7bedf..b95732e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -53,7 +53,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { tapl.goHome() }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 95121de..cdbdb85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -62,7 +62,7 @@
*/
@Presubmit
@Test
- fun pipWindowRemainInsideVisibleBounds() {
+ open fun pipWindowRemainInsideVisibleBounds() {
flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index dba21b8..1477cf7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -89,6 +89,8 @@
@Mock lateinit var transitions: Transitions
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+ @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
+ ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
private lateinit var mockitoSession: StaticMockitoSession
@@ -129,6 +131,7 @@
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
+ mToggleResizeDesktopTaskTransitionHandler,
desktopModeTaskRepository,
launchAdjacentController,
shellExecutor
@@ -270,8 +273,9 @@
}
@Test
- fun moveToDesktop() {
+ fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToDesktop(task)
val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -279,6 +283,16 @@
}
@Test
+ fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+ val task = setUpFullscreenTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveToDesktop(task)
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
fun moveToDesktop_nonExistentTask_doesNothing() {
controller.moveToDesktop(999)
verifyWCTNotExecuted()
@@ -325,12 +339,23 @@
}
@Test
- fun moveToFullscreen() {
+ fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToFullscreen(task)
val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+ val task = setUpFreeformTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveToFullscreen(task)
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 81fc843..1b38956 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -128,8 +128,8 @@
doReturn(true).when(mTransitions).isRegistered();
}
mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
- mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer,
- mTaskViewTransitions, mSyncQueue);
+ mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
+ mTaskViewTransitions, mSyncQueue));
mTaskView = new TaskView(mContext, mTaskViewTaskController);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -544,4 +544,23 @@
mTaskView.removeTask();
verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
}
+
+ @Test
+ public void testOnTaskAppearedWithTaskNotFound() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTaskController.setTaskNotFound();
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+
+ verify(mTaskViewTaskController).cleanUpPendingTask();
+ verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+ }
+
+ @Test
+ public void testOnTaskAppeared_withoutTaskNotFound() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+ verify(mTaskViewTaskController, never()).cleanUpPendingTask();
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 9d56686..0504439 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.taskview;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.google.common.truth.Truth.assertThat;
@@ -25,16 +26,19 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.graphics.Rect;
+import android.os.IBinder;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.transition.Transitions;
@@ -45,6 +49,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -198,4 +205,34 @@
mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
new Rect(0, 0, 100, 100));
}
+
+ @Test
+ public void test_startAnimation_setsTaskNotFound() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ when(change.getTaskInfo()).thenReturn(mTaskInfo);
+ when(change.getMode()).thenReturn(TRANSIT_OPEN);
+
+ List<TransitionInfo.Change> changes = new ArrayList<>();
+ changes.add(change);
+
+ TransitionInfo info = mock(TransitionInfo.class);
+ when(info.getChanges()).thenReturn(changes);
+
+ mTaskViewTransitions.startTaskView(new WindowContainerTransaction(),
+ mTaskViewTaskController,
+ mock(IBinder.class));
+
+ TaskViewTransitions.PendingTransition pending =
+ mTaskViewTransitions.findPendingOpeningTransition(mTaskViewTaskController);
+
+ mTaskViewTransitions.startAnimation(pending.mClaimed,
+ info,
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(mTaskViewTaskController).setTaskNotFound();
+ }
}
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 963632b..961e3e9 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
@@ -51,7 +51,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.after;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
@@ -93,7 +92,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.testutils.StubTransaction;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.TransitionInfoBuilder;
@@ -105,6 +103,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
+import com.android.wm.shell.util.StubTransaction;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java
new file mode 100644
index 0000000..855f541
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.InputWindowHandle;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+/**
+ * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit
+ * testing to avoid calls to native code.
+ *
+ * Note: This is a copy of com.android.server.testutils.StubTransaction
+ */
+public class StubTransaction extends SurfaceControl.Transaction {
+
+ private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>();
+
+ @Override
+ public void apply() {
+ for (Runnable listener : mWindowInfosReportedListeners) {
+ listener.run();
+ }
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void apply(boolean sync) {
+ apply();
+ }
+
+ @Override
+ public SurfaceControl.Transaction setVisibility(SurfaceControl sc, boolean visible) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction show(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction hide(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setPosition(SurfaceControl sc, float x, float y) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setBufferSize(SurfaceControl sc,
+ int w, int h) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setLayer(SurfaceControl sc, int z) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo,
+ int z) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setTransparentRegionHint(SurfaceControl sc,
+ Region transparentRegion) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setAlpha(SurfaceControl sc, float alpha) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setInputWindowInfo(SurfaceControl sc,
+ InputWindowHandle handle) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setGeometry(SurfaceControl sc, Rect sourceCrop,
+ Rect destFrame, @Surface.Rotation int orientation) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMatrix(SurfaceControl sc,
+ float dsdx, float dtdx, float dtdy, float dsdy) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorTransform(SurfaceControl sc, float[] matrix,
+ float[] translation) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SurfaceControl.Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColor(SurfaceControl sc, float[] color) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplayFlags(IBinder displayToken, int flags) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setAnimationTransaction() {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMetadata(SurfaceControl sc, int key, int data) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMetadata(SurfaceControl sc, int key, Parcel data) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction merge(SurfaceControl.Transaction other) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction remove(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor,
+ SurfaceControl.TransactionCommittedListener listener) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorSpaceAgnostic(SurfaceControl sc, boolean agnostic) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc,
+ int priority) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate,
+ int compatibility, int changeFrameRateStrategy) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setFixedTransformHint(SurfaceControl sc,
+ @Surface.Rotation int transformHint) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SurfaceControl.Transaction setBuffer(@NonNull SurfaceControl sc,
+ @Nullable HardwareBuffer buffer) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setTrustedOverlay(SurfaceControl sc,
+ boolean isTrustedOverlay) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction addWindowInfosReportedListener(@NonNull Runnable listener) {
+ mWindowInfosReportedListeners.add(listener);
+ return this;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
index 8f84008..3fbab0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -55,7 +55,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+ `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
dragDetector = DragDetector(eventHandler)
dragDetector.setTouchSlop(SLOP)
@@ -72,13 +72,13 @@
@Test
fun testNoMove_passesDownAndUp() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -86,12 +86,12 @@
@Test
fun testMoveInSlop_touch_passesDownAndUp() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -99,12 +99,12 @@
val newX = X + SLOP - 1
assertFalse(
dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
- verify(eventHandler, never()).handleMotionEvent(argThat {
+ verify(eventHandler, never()).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_MOVE
})
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -112,13 +112,13 @@
@Test
fun testMoveInSlop_mouse_passesDownMoveAndUp() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE
})
@@ -126,14 +126,14 @@
val newX = X + SLOP - 1
assertTrue(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE
})
assertTrue(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE
})
@@ -141,25 +141,25 @@
@Test
fun testMoveBeyondSlop_passesDownMoveAndUp() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
val newX = X + SLOP + 1
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -167,12 +167,12 @@
@Test
fun testPassesHoverEnter() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_HOVER_ENTER
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
})
}
@@ -180,7 +180,7 @@
@Test
fun testPassesHoverMove() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
})
}
@@ -188,7 +188,7 @@
@Test
fun testPassesHoverExit() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
})
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index f941e95..7fc1c99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -57,7 +57,6 @@
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -411,15 +410,17 @@
verify(additionalWindowSurfaceBuilder).build();
verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
final int width = WindowDecoration.loadDimensionPixelSize(
- mContext.getResources(), mCaptionMenuWidthId);
+ windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId);
final int height = WindowDecoration.loadDimensionPixelSize(
- mContext.getResources(), mRelayoutParams.mCaptionHeightId);
+ windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
- final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+ final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
+ windowDecor.mDecorWindowContext.getResources(),
mCaptionMenuShadowRadiusId);
verify(mMockSurfaceControlAddWindowT)
.setShadowRadius(additionalWindowSurface, shadowRadius);
- final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+ final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
+ windowDecor.mDecorWindowContext.getResources(),
mCaptionMenuCornerRadiusId);
verify(mMockSurfaceControlAddWindowT)
.setCornerRadius(additionalWindowSurface, cornerRadius);
@@ -514,8 +515,7 @@
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
- return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
- mMockDisplayController, mMockShellTaskOrganizer,
+ return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, testSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index 0f8a85d..cec0ee7 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -86,6 +86,16 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
}
+jlong Gainmap_createCopy(JNIEnv*, jobject, jlong sourcePtr) {
+ Gainmap* gainmap = new Gainmap();
+ gainmap->incStrong(0);
+ if (sourcePtr) {
+ Gainmap* src = fromJava(sourcePtr);
+ gainmap->info = src->info;
+ }
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
+}
+
static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
@@ -237,6 +247,7 @@
static const JNINativeMethod gGainmapMethods[] = {
{"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
{"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
+ {"nCreateCopy", "(J)J", (void*)Gainmap_createCopy},
{"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
{"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
{"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 16b35ff..a5518eb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -405,8 +405,17 @@
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
- if (!wasSkipped(mCurrentFrameInfo)) {
+ if (wasSkipped(mCurrentFrameInfo)) {
+ // Use the oldest skipped frame in case we skip more than a single frame
+ if (!mSkippedFrameInfo) {
+ mSkippedFrameInfo.emplace();
+ mSkippedFrameInfo->vsyncId =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ }
+ } else {
mCurrentFrameInfo = mJankTracker.startFrame();
+ mSkippedFrameInfo.reset();
}
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
@@ -602,10 +611,18 @@
if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
const auto inputEventId =
static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
- native_window_set_frame_timeline_info(
- mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId,
- mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
- solelyTextureViewUpdates);
+ const ANativeWindowFrameTimelineInfo ftl = {
+ .frameNumber = frameCompleteNr,
+ .frameTimelineVsyncId = vsyncId,
+ .inputEventId = inputEventId,
+ .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
+ .useForRefreshRateSelection = solelyTextureViewUpdates,
+ .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId
+ : UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ .skippedFrameStartTimeNanos =
+ mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0,
+ };
+ native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl);
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 5219b57..32ac5af 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -366,6 +366,12 @@
ColorMode mColorMode = ColorMode::Default;
float mTargetSdrHdrRatio = 1.f;
+
+ struct SkippedFrameInfo {
+ int64_t vsyncId;
+ int64_t startTime;
+ };
+ std::optional<SkippedFrameInfo> mSkippedFrameInfo;
};
} /* namespace renderthread */
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 3394dd0..e8adfaf 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -134,9 +134,11 @@
/**
* @hide
- * @param audioAttributes to identify AudioProductStrategy with
- * @return legacy stream type associated with matched AudioProductStrategy
- * Defaults to STREAM_MUSIC if no match is found, or if matches is STREAM_DEFAULT
+ * @param audioAttributes to identify {@link AudioProductStrategy} with
+ * @return legacy stream type associated with matched {@link AudioProductStrategy}. If no
+ * strategy found or found {@link AudioProductStrategy} does not have associated
+ * legacy stream (i.e. associated with {@link AudioSystem#STREAM_DEFAULT}) defaults
+ * to {@link AudioSystem#STREAM_MUSIC}
*/
public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
@NonNull AudioAttributes audioAttributes) {
@@ -147,9 +149,9 @@
int streamType = productStrategy.getLegacyStreamTypeForAudioAttributes(
audioAttributes);
if (streamType == AudioSystem.STREAM_DEFAULT) {
- Log.w(TAG, "Attributes " + audioAttributes.toString() + " ported by strategy "
- + productStrategy.getId() + " has no stream type associated, "
- + "DO NOT USE STREAM TO CONTROL THE VOLUME");
+ Log.w(TAG, "Attributes " + audioAttributes + " supported by strategy "
+ + productStrategy.getId() + " have no associated stream type, "
+ + "therefore falling back to STREAM_MUSIC");
return AudioSystem.STREAM_MUSIC;
}
if (streamType < AudioSystem.getNumStreamTypes()) {
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index c52fde6..4eaa39b 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,11 +38,11 @@
<!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update">Do you want to update this app?</string>
<!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder" product="tablet">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.</string>
+ <string name="install_confirm_question_update_owner_reminder" product="tablet"><p>Update this app from <b><xliff:g id="new_update_owner">%1$s</xliff:g></b>\?</p><p>This app normally receives updates from <b><xliff:g id="existing_update_owner">%2$s</xliff:g></b>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.</p></string>
<!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder" product="tv">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.</string>
+ <string name="install_confirm_question_update_owner_reminder" product="tv"><p>Update this app from <b><xliff:g id="new_update_owner">%1$s</xliff:g></b>\?</p><p>This app normally receives updates from <b><xliff:g id="existing_update_owner">%2$s</xliff:g></b>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.</p></string>
<!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder" product="default">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
+ <string name="install_confirm_question_update_owner_reminder" product="default"><p>Update this app from <b><xliff:g id="new_update_owner">%1$s</xliff:g></b>\?</p><p>This app normally receives updates from <b><xliff:g id="existing_update_owner">%2$s</xliff:g></b>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</p></string>
<!-- [CHAR LIMIT=100] -->
<string name="install_failed">App not installed.</string>
<!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e071c11..411f1fa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -49,6 +49,8 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.Html;
+import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -144,9 +146,12 @@
final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
&& mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
- viewToEnable.setText(
+ String updateOwnerString =
getString(R.string.install_confirm_question_update_owner_reminder,
- requestedUpdateOwnerLabel, existingUpdateOwnerLabel));
+ requestedUpdateOwnerLabel, existingUpdateOwnerLabel);
+ Spanned styledUpdateOwnerString =
+ Html.fromHtml(updateOwnerString, Html.FROM_HTML_MODE_LEGACY);
+ viewToEnable.setText(styledUpdateOwnerString);
mOk.setText(R.string.update_anyway);
} else {
mOk.setText(R.string.update);
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
deleted file mode 100644
index e68ef85..0000000
--- a/packages/SettingsLib/Spa/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-buildscript {
- ext {
- BUILD_TOOLS_VERSION = "30.0.3"
- MIN_SDK = 21
- TARGET_SDK = 33
- jetpack_compose_version = '1.4.0-beta01'
- jetpack_compose_compiler_version = '1.4.4'
- }
-}
-plugins {
- id 'com.android.application' version '8.0.0' apply false
- id 'com.android.library' version '8.0.0' apply false
- id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
-}
-subprojects {
- tasks.withType(KotlinCompile).configureEach {
- kotlinOptions {
- jvmTarget = "17"
- freeCompilerArgs = ["-Xjvm-default=all"]
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
new file mode 100644
index 0000000..64b67d7
--- /dev/null
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.api.AndroidBasePlugin
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.kotlin.android) apply false
+}
+
+allprojects {
+ extra["jetpackComposeVersion"] = "1.4.0-beta01"
+}
+
+subprojects {
+ plugins.withType<AndroidBasePlugin> {
+ configure<BaseExtension> {
+ compileSdkVersion(33)
+
+ defaultConfig {
+ minSdk = 21
+ targetSdk = 34
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ }
+ }
+
+ afterEvaluate {
+ plugins.withType<AndroidBasePlugin> {
+ configure<BaseExtension> {
+ if (buildFeatures.compose == true) {
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.4.4"
+ }
+ }
+ }
+ }
+ }
+
+ tasks.withType<KotlinCompile> {
+ kotlinOptions {
+ jvmTarget = "17"
+ freeCompilerArgs = listOf("-Xjvm-default=all")
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
deleted file mode 100644
index 212aa7b..0000000
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
- id 'com.android.application'
- id 'kotlin-android'
-}
-
-android {
- namespace 'com.android.settingslib.spa.gallery'
- compileSdk TARGET_SDK
- buildToolsVersion = BUILD_TOOLS_VERSION
-
- defaultConfig {
- applicationId "com.android.settingslib.spa.gallery"
- minSdk MIN_SDK
- targetSdk TARGET_SDK
- versionCode 1
- versionName "1.0"
- }
-
- sourceSets {
- main {
- kotlin {
- srcDir "src"
- }
- res.srcDirs = ["res"]
- manifest.srcFile "AndroidManifest.xml"
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion jetpack_compose_compiler_version
- }
-}
-
-dependencies {
- implementation project(":spa")
-}
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle.kts b/packages/SettingsLib/Spa/gallery/build.gradle.kts
new file mode 100644
index 0000000..7f689c1
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/build.gradle.kts
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.android.settingslib.spa.gallery"
+
+ defaultConfig {
+ applicationId = "com.android.settingslib.spa.gallery"
+ versionCode = 1
+ versionName = "1.0"
+ }
+
+ sourceSets {
+ sourceSets.getByName("main") {
+ java.setSrcDirs(listOf("src"))
+ res.setSrcDirs(listOf("res"))
+ manifest.srcFile("AndroidManifest.xml")
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+}
+
+dependencies {
+ implementation(project(":spa"))
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
new file mode 100644
index 0000000..9a16df8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[versions]
+agp = "8.0.2"
+dexmaker-mockito = "2.28.3"
+kotlin = "1.8.10"
+truth = "1.1"
+
+[libraries]
+dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
+truth = { module = "com.google.truth:truth", version.ref = "truth" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+android-library = { id = "com.android.library", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index ed85e33..33f49e3 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -14,9 +14,8 @@
# limitations under the License.
#
-#Thu Jul 14 10:36:06 CST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle.kts
similarity index 77%
rename from packages/SettingsLib/Spa/settings.gradle
rename to packages/SettingsLib/Spa/settings.gradle.kts
index 1c5a1ce..9909781 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle.kts
@@ -16,20 +16,27 @@
pluginManagement {
repositories {
- gradlePluginPortal()
google()
mavenCentral()
+ gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ rulesMode.set(RulesMode.FAIL_ON_PROJECT_RULES)
+
repositories {
google()
mavenCentral()
- maven { url "https://jitpack.io"}
+ maven {
+ url = uri("https://jitpack.io")
+ content {
+ includeGroup("com.github.PhilJay")
+ }
+ }
}
}
rootProject.name = "SpaLib"
-include ':spa'
-include ':gallery'
-include ':testutils'
+include(":spa")
+include(":gallery")
+include(":testutils")
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
deleted file mode 100644
index a591366..0000000
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
- id 'com.android.library'
- id 'kotlin-android'
-}
-
-android {
- namespace 'com.android.settingslib.spa'
- compileSdk TARGET_SDK
- buildToolsVersion = BUILD_TOOLS_VERSION
-
- defaultConfig {
- minSdk MIN_SDK
- targetSdk TARGET_SDK
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- sourceSets {
- main {
- kotlin {
- srcDir "src"
- }
- res.srcDirs = ["res"]
- manifest.srcFile "AndroidManifest.xml"
- }
- androidTest {
- kotlin {
- srcDir "../tests/src"
- }
- res.srcDirs = ["../tests/res"]
- manifest.srcFile "../tests/AndroidManifest.xml"
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion jetpack_compose_compiler_version
- }
- buildTypes {
- debug {
- testCoverageEnabled = true
- }
- }
-}
-
-dependencies {
- api "androidx.appcompat:appcompat:1.7.0-alpha02"
- api "androidx.slice:slice-builders:1.1.0-alpha02"
- api "androidx.slice:slice-core:1.1.0-alpha02"
- api "androidx.slice:slice-view:1.1.0-alpha02"
- api "androidx.compose.material3:material3:1.1.0-alpha06"
- api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
- api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
- api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx"
- api "androidx.lifecycle:lifecycle-runtime-compose"
- api "androidx.navigation:navigation-compose:2.6.0-alpha08"
- api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
- api "com.google.android.material:material:1.7.0-alpha03"
- debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
- implementation "com.airbnb.android:lottie-compose:5.2.0"
-
- androidTestImplementation project(":testutils")
- androidTestImplementation 'androidx.lifecycle:lifecycle-runtime-testing'
- androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.3"
-}
-
-task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
- group = "Reporting"
- description = "Generate Jacoco coverage reports after running tests."
-
- sourceDirectories.from = files("src")
- classDirectories.from = fileTree(
- dir: "$buildDir/tmp/kotlin-classes/debug",
- excludes: [
- "com/android/settingslib/spa/debug/**",
-
- // Excludes files forked from AndroidX.
- "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
- "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
-
- // Excludes files forked from Accompanist.
- "com/android/settingslib/spa/framework/compose/DrawablePainter*",
-
- // Excludes inline functions, which is not covered in Jacoco reports.
- "com/android/settingslib/spa/framework/util/Collections*",
- "com/android/settingslib/spa/framework/util/Flows*",
-
- // Excludes debug functions
- "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
-
- // Excludes slice demo presenter & provider
- "com/android/settingslib/spa/slice/presenter/Demo*",
- "com/android/settingslib/spa/slice/provider/Demo*",
- ],
- )
- executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
-}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
new file mode 100644
index 0000000..fac63361
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+plugins {
+ id(libs.plugins.android.library.get().pluginId)
+ id(libs.plugins.kotlin.android.get().pluginId)
+ jacoco
+}
+
+val jetpackComposeVersion: String? by extra
+
+android {
+ namespace = "com.android.settingslib.spa"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ sourceSets {
+ sourceSets.getByName("main") {
+ kotlin.setSrcDirs(listOf("src"))
+ res.setSrcDirs(listOf("res"))
+ manifest.srcFile("AndroidManifest.xml")
+ }
+ sourceSets.getByName("androidTest") {
+ kotlin.setSrcDirs(listOf("../tests/src"))
+ res.setSrcDirs(listOf("../tests/res"))
+ manifest.srcFile("../tests/AndroidManifest.xml")
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+ buildTypes {
+ getByName("debug") {
+ enableAndroidTestCoverage = true
+ }
+ }
+}
+
+dependencies {
+ api("androidx.appcompat:appcompat:1.7.0-alpha02")
+ api("androidx.slice:slice-builders:1.1.0-alpha02")
+ api("androidx.slice:slice-core:1.1.0-alpha02")
+ api("androidx.slice:slice-view:1.1.0-alpha02")
+ api("androidx.compose.material3:material3:1.1.0-alpha06")
+ api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
+ api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
+ api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
+ api("androidx.lifecycle:lifecycle-livedata-ktx")
+ api("androidx.lifecycle:lifecycle-runtime-compose")
+ api("androidx.navigation:navigation-compose:2.6.0-alpha08")
+ api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
+ api("com.google.android.material:material:1.7.0-alpha03")
+ debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
+ implementation("com.airbnb.android:lottie-compose:5.2.0")
+
+ androidTestImplementation(project(":testutils"))
+ androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing")
+}
+
+tasks.register<JacocoReport>("coverageReport") {
+ group = "Reporting"
+ description = "Generate Jacoco coverage reports after running tests."
+ dependsOn("connectedDebugAndroidTest")
+ sourceDirectories.setFrom(files("src"))
+ classDirectories.setFrom(
+ fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")) {
+ setExcludes(
+ listOf(
+ "com/android/settingslib/spa/debug/**",
+
+ // Excludes files forked from AndroidX.
+ "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+ "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+ // Excludes files forked from Accompanist.
+ "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+
+ // Excludes inline functions, which is not covered in Jacoco reports.
+ "com/android/settingslib/spa/framework/util/Collections*",
+ "com/android/settingslib/spa/framework/util/Flows*",
+
+ // Excludes debug functions
+ "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
+
+ // Excludes slice demo presenter & provider
+ "com/android/settingslib/spa/slice/presenter/Demo*",
+ "com/android/settingslib/spa/slice/provider/Demo*",
+ )
+ )
+ }
+ )
+ executionData.setFrom(
+ fileTree(layout.buildDirectory.dir("outputs/code_coverage/debugAndroidTest/connected"))
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
index 81bbc24..57bb838 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
@@ -18,7 +18,7 @@
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
@@ -78,10 +78,14 @@
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.Center,
route: String? = null,
- enterTransition: (AnimatedScope.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) },
- exitTransition: (AnimatedScope.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) },
- popEnterTransition: (AnimatedScope.() -> EnterTransition) = enterTransition,
- popExitTransition: (AnimatedScope.() -> ExitTransition) = exitTransition,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ enterTransition,
+ popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ exitTransition,
builder: NavGraphBuilder.() -> Unit
) {
AnimatedNavHost(
@@ -119,10 +123,14 @@
graph: NavGraph,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.Center,
- enterTransition: (AnimatedScope.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) },
- exitTransition: (AnimatedScope.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) },
- popEnterTransition: (AnimatedScope.() -> EnterTransition) = enterTransition,
- popExitTransition: (AnimatedScope.() -> ExitTransition) = exitTransition,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ enterTransition,
+ popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ exitTransition,
) {
val lifecycleOwner = LocalLifecycleOwner.current
@@ -160,7 +168,7 @@
val backStackEntry = visibleEntries.lastOrNull()
if (backStackEntry != null) {
- val finalEnter: AnimatedScope.() -> EnterTransition = {
+ val finalEnter: AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition = {
val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination
if (composeNavigator.isPop.value) {
@@ -174,7 +182,7 @@
}
}
- val finalExit: AnimatedScope.() -> ExitTransition = {
+ val finalExit: AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition = {
val initialDestination =
initialState.destination as AnimatedComposeNavigator.Destination
@@ -237,16 +245,19 @@
DialogHost(dialogNavigator)
}
-@OptIn(ExperimentalAnimationApi::class)
-internal typealias AnimatedScope = AnimatedContentTransitionScope<NavBackStackEntry>
+@ExperimentalAnimationApi
+internal val enterTransitions =
+ mutableMapOf<String?,
+ (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
@ExperimentalAnimationApi
-internal val enterTransitions = mutableMapOf<String?, (AnimatedScope.() -> EnterTransition?)?>()
+internal val exitTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
@ExperimentalAnimationApi
-internal val exitTransitions = mutableMapOf<String?, (AnimatedScope.() -> ExitTransition?)?>()
-@ExperimentalAnimationApi
-internal val popEnterTransitions = mutableMapOf<String?, (AnimatedScope.() -> EnterTransition?)?>()
+internal val popEnterTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
@ExperimentalAnimationApi
-internal val popExitTransitions = mutableMapOf<String?, (AnimatedScope.() -> ExitTransition?)?>()
+internal val popExitTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
index bf92f5d..9e58603 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.compose
+import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
@@ -24,7 +25,9 @@
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraph
import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.navigation
import androidx.navigation.get
/**
@@ -44,10 +47,14 @@
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
- enterTransition: (AnimatedScope.() -> EnterTransition?)? = null,
- exitTransition: (AnimatedScope.() -> ExitTransition?)? = null,
- popEnterTransition: (AnimatedScope.() -> EnterTransition?)? = enterTransition,
- popExitTransition: (AnimatedScope.() -> ExitTransition?)? = exitTransition,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
) {
addDestination(
@@ -69,3 +76,43 @@
}
)
}
+
+/**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to define enter transitions for destination in this NavGraph
+ * @param exitTransition callback to define exit transitions for destination in this NavGraph
+ * @param popEnterTransition callback to define pop enter transitions for destination in this
+ * NavGraph
+ * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+@ExperimentalAnimationApi
+public fun NavGraphBuilder.navigation(
+ startDestination: String,
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
+ builder: NavGraphBuilder.() -> Unit
+) {
+ navigation(startDestination, route, arguments, deepLinks, builder).apply {
+ enterTransition?.let { enterTransitions[route] = enterTransition }
+ exitTransition?.let { exitTransitions[route] = exitTransition }
+ popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
+ popExitTransition?.let { popExitTransitions[route] = popExitTransition }
+ }
+}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
deleted file mode 100644
index 23a9add..0000000
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
- id 'com.android.library'
- id 'kotlin-android'
-}
-
-android {
- namespace 'com.android.settingslib.spa.testutils'
- compileSdk TARGET_SDK
- buildToolsVersion = BUILD_TOOLS_VERSION
-
- defaultConfig {
- minSdk MIN_SDK
- targetSdk TARGET_SDK
- }
-
- sourceSets {
- main {
- kotlin {
- srcDir "src"
- }
- manifest.srcFile "AndroidManifest.xml"
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion jetpack_compose_compiler_version
- }
-}
-
-dependencies {
- api project(":spa")
-
- api "androidx.arch.core:core-testing:2.2.0-alpha01"
- api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
- api "com.google.truth:truth:1.1.3"
- api "org.mockito:mockito-core:2.21.0"
- debugApi "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
-}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
new file mode 100644
index 0000000..c3df9bc
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+plugins {
+ id(libs.plugins.android.library.get().pluginId)
+ id(libs.plugins.kotlin.android.get().pluginId)
+}
+
+val jetpackComposeVersion: String? by extra
+
+android {
+ namespace = "com.android.settingslib.spa.testutils"
+
+ sourceSets {
+ sourceSets.getByName("main") {
+ java.setSrcDirs(listOf("src"))
+ manifest.srcFile("AndroidManifest.xml")
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+}
+
+dependencies {
+ api(project(":spa"))
+
+ api("androidx.arch.core:core-testing:2.2.0-alpha01")
+ api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
+ api(libs.truth)
+ api(libs.dexmaker.mockito)
+ debugApi("androidx.compose.ui:ui-test-manifest:$jetpackComposeVersion")
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 675a7d0..674894d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -370,7 +370,7 @@
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
tools:replace="android:appComponentFactory"
- android:appComponentFactory=".SystemUIAppComponentFactory">
+ android:appComponentFactory=".PhoneSystemUIAppComponentFactory">
<!-- Keep theme in sync with SystemUIApplication.onCreate().
Setting the theme on the application does not affect views inflated by services.
The application theme is set again from onCreate to take effect for those views. -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 7e1bfb9..addabcc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -21,7 +21,6 @@
import android.util.Log
import android.util.LruCache
import android.util.MathUtils
-import android.util.MathUtils.abs
import androidx.annotation.VisibleForTesting
import java.lang.Float.max
import java.lang.Float.min
@@ -30,8 +29,6 @@
private const val TAG_ITAL = "ital"
private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
-private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100
-
private const val FONT_ITALIC_MAX = 1f
private const val FONT_ITALIC_MIN = 0f
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
@@ -39,11 +36,12 @@
// Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in
// frame draw time on a Pixel 6.
-@VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10
+@VisibleForTesting const val DEFAULT_FONT_CACHE_MAX_ENTRIES = 10
/** Provide interpolation of two fonts by adjusting font variation settings. */
-class FontInterpolator {
-
+class FontInterpolator(
+ numberOfAnimationSteps: Int? = null,
+) {
/**
* Cache key for the interpolated font.
*
@@ -88,8 +86,9 @@
// Font interpolator has two level caches: one for input and one for font with different
// variation settings. No synchronization is needed since FontInterpolator is not designed to be
// thread-safe and can be used only on UI thread.
- private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES)
- private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES)
+ val cacheMaxEntries = numberOfAnimationSteps?.let { it * 2 } ?: DEFAULT_FONT_CACHE_MAX_ENTRIES
+ private val interpCache = LruCache<InterpKey, Font>(cacheMaxEntries)
+ private val verFontCache = LruCache<VarFontKey, Font>(cacheMaxEntries)
// Mutable keys for recycling.
private val tmpInterpKey = InterpKey(null, null, 0f)
@@ -128,18 +127,12 @@
val newAxes =
lerp(startAxes, endAxes) { tag, startValue, endValue ->
when (tag) {
- // TODO: Good to parse 'fvar' table for retrieving default value.
- TAG_WGHT -> {
- adaptiveAdjustWeight(
- MathUtils.lerp(
- startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
- endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
- progress
- ),
+ TAG_WGHT ->
+ MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
+ progress
)
- }
TAG_ITAL ->
adjustItalic(
MathUtils.lerp(
@@ -175,9 +168,9 @@
val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
interpCache.put(InterpKey(start, end, progress), newFont)
verFontCache.put(VarFontKey(start, newAxes), newFont)
- if (DEBUG) {
- Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey")
- }
+
+ // Cache misses are likely to create memory leaks, so this is logged at error level.
+ Log.e(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey")
return newFont
}
@@ -225,15 +218,6 @@
return result
}
- // For the performance reasons, we animate weight with adaptive step. This helps
- // Cache hit ratio in the Skia glyph cache.
- // The reason we don't use fix step is because the range of weight axis is not normalized,
- // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step
- private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float {
- val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F)
- return coerceInWithStep(value, min(start, end), max(start, end), step)
- }
-
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
private fun adjustItalic(value: Float) =
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 16ddf0c..b555fa5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -108,7 +108,8 @@
}
// Following two members are for mutable for testing purposes.
- public var textInterpolator: TextInterpolator = TextInterpolator(layout, typefaceCache)
+ public var textInterpolator: TextInterpolator =
+ TextInterpolator(layout, typefaceCache, numberOfAnimationSteps)
public var animator: ValueAnimator =
ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 8ed8d8f..02caeed 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -31,6 +31,7 @@
class TextInterpolator(
layout: Layout,
var typefaceCache: TypefaceVariantCache,
+ numberOfAnimationSteps: Int? = null,
) {
/**
* Returns base paint used for interpolation.
@@ -85,7 +86,7 @@
private class Line(val runs: List<Run>)
private var lines = listOf<Line>()
- private val fontInterpolator = FontInterpolator()
+ private val fontInterpolator = FontInterpolator(numberOfAnimationSteps)
// Recycling object for glyph drawing and tweaking.
private val tmpPaint = TextPaint()
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index 34d31d9..4e39f1a 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -25,6 +25,9 @@
sdk_version: "current",
name: "PluginCoreLib",
srcs: ["src/**/*.java"],
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
// Enforce that the library is built against java 8 so that there are
// no compatibility issues with launcher
diff --git a/packages/SystemUI/plugin_core/proguard.flags b/packages/SystemUI/plugin_core/proguard.flags
new file mode 100644
index 0000000..6240898
--- /dev/null
+++ b/packages/SystemUI/plugin_core/proguard.flags
@@ -0,0 +1,11 @@
+# R8's full mode is a bit more aggressive in stripping annotations, but the
+# SystemUI plugin architecture requires these annotations at runtime. The
+# following rules are the minimal set necessary to ensure compatibility.
+# For more details, see:
+# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode
+-keepattributes RuntimeVisible*Annotation*,AnnotationDefault
+
+-keep interface com.android.systemui.plugins.annotations.** {
+ *;
+}
+-keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class *
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a8ed843..b534fcec 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,13 +1,7 @@
-include proguard_common.flags
--keep class com.android.systemui.statusbar.tv.TvStatusBar
-keep class com.android.systemui.SystemUIInitializerImpl {
*;
}
--keep class com.android.systemui.tv.TvSystemUIInitializer {
- *;
-}
-
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.DaggerTvGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 0ca154e..2ace86f 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -20,11 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- SystemUIFactory component -->
- <string name="config_systemUIFactoryComponent" translatable="false">
- com.android.systemui.tv.TvSystemUIInitializer
- </string>
-
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
<integer name="recents_svelte_level">3</integer>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 166bd2a..421f41f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -302,9 +302,6 @@
<!-- Determines whether the shell features all run on another thread. -->
<bool name="config_enableShellMainThread">true</bool>
- <!-- SystemUIFactory component -->
- <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
-
<!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
<dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
<dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 26b824a..25f3d22 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2817,6 +2817,8 @@
<!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
<string name="qs_alarm_tile_no_alarm">No alarm set</string>
+ <!-- Accessibility label for a11y action to show the bouncer (pin/pattern/password) screen lock [CHAR LIMIT=NONE] -->
+ <string name="accessibility_bouncer">enter screen lock</string>
<!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
<string name="accessibility_fingerprint_label">Fingerprint sensor</string>
<!-- Accessibility action for tapping on an affordance that will bring up the user's
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 1f61c64..97d6099 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -215,7 +215,7 @@
mSubscriptions.put(token, state);
// Add and associate conditions.
- normalizedCondition.getConditions().stream().forEach(condition -> {
+ normalizedCondition.getConditions().forEach(condition -> {
if (!mConditions.containsKey(condition)) {
mConditions.put(condition, new ArraySet<>());
condition.addCallback(mConditionCallback);
@@ -321,7 +321,6 @@
private final Callback mCallback;
private final Subscription mNestedSubscription;
private final ArraySet<Condition> mConditions;
- private final ArraySet<Condition> mPreconditions;
/**
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
@@ -337,8 +336,7 @@
private Builder(Subscription nestedSubscription, Callback callback) {
mNestedSubscription = nestedSubscription;
mCallback = callback;
- mConditions = new ArraySet();
- mPreconditions = new ArraySet();
+ mConditions = new ArraySet<>();
}
/**
@@ -352,29 +350,6 @@
}
/**
- * Adds a set of {@link Condition} to be a precondition for {@link Subscription}.
- *
- * @return The updated {@link Builder}.
- */
- public Builder addPreconditions(Set<Condition> condition) {
- if (condition == null) {
- return this;
- }
- mPreconditions.addAll(condition);
- return this;
- }
-
- /**
- * Adds a {@link Condition} to be a precondition for {@link Subscription}.
- *
- * @return The updated {@link Builder}.
- */
- public Builder addPrecondition(Condition condition) {
- mPreconditions.add(condition);
- return this;
- }
-
- /**
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
*
* @return The updated {@link Builder}.
@@ -394,11 +369,7 @@
* @return The resulting {@link Subscription}.
*/
public Subscription build() {
- final Subscription subscription =
- new Subscription(mConditions, mCallback, mNestedSubscription);
- return !mPreconditions.isEmpty()
- ? new Subscription(mPreconditions, null, subscription)
- : subscription;
+ return new Subscription(mConditions, mCallback, mNestedSubscription);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 44bd8c7..64ab758 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -155,6 +155,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
@@ -380,6 +382,7 @@
private final ActiveUnlockConfig mActiveUnlockConfig;
private final IDreamManager mDreamManager;
private final TelephonyManager mTelephonyManager;
+ private final FeatureFlags mFeatureFlags;
@Nullable
private final FingerprintManager mFpm;
@Nullable
@@ -2290,7 +2293,8 @@
@Nullable BiometricManager biometricManager,
FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
DevicePostureController devicePostureController,
- Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) {
+ Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
+ FeatureFlags featureFlags) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2322,6 +2326,7 @@
mPackageManager = packageManager;
mFpm = fingerprintManager;
mFaceManager = faceManager;
+ mFeatureFlags = featureFlags;
mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
mFaceAcquiredInfoIgnoreList = Arrays.stream(
mContext.getResources().getIntArray(
@@ -3013,7 +3018,12 @@
|| shouldListenForFingerprintAssistant
|| (mKeyguardOccluded && mIsDreaming)
|| (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
- && (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing));
+ && (mOccludingAppRequestingFp
+ || isUdfps
+ || mAlternateBouncerShowing
+ || mFeatureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)
+ )
+ );
// Only listen if this KeyguardUpdateMonitor belongs to the system user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -3170,6 +3180,13 @@
|| (posture == mConfigFaceAuthSupportedPosture);
}
+ /**
+ * If the current device posture allows face auth to run.
+ */
+ public boolean doesCurrentPostureAllowFaceAuth() {
+ return doesPostureAllowFaceAuth(mPostureState);
+ }
+
private void logListenerModelData(@NonNull KeyguardListenModel model) {
mLogger.logKeyguardListenerModel(model);
if (model instanceof KeyguardFingerprintListenModel) {
@@ -3655,7 +3672,8 @@
mLogger.logSimState(subId, slotId, state);
boolean becameAbsent = false;
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)
+ && state != TelephonyManager.SIM_STATE_UNKNOWN) {
mLogger.w("invalid subId in handleSimStateChange()");
/* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
* handleServiceStateChange() handle other case */
@@ -3690,7 +3708,7 @@
data.subId = subId;
data.slotId = slotId;
}
- if ((changed || becameAbsent) && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+ if ((changed || becameAbsent) || state == TelephonyManager.SIM_STATE_UNKNOWN) {
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 2b3b3c6..56c0953 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -30,6 +30,7 @@
import android.transition.TransitionValues;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -51,12 +52,30 @@
private int mPosition = 0;
private final PinShapeAdapter mPinShapeAdapter;
private ValueAnimator mValueAnimator = ValueAnimator.ofFloat(1f, 0f);
+ private Rect mFirstChildVisibleRect = new Rect();
public PinShapeNonHintingView(Context context, AttributeSet attrs) {
super(context, attrs);
mPinShapeAdapter = new PinShapeAdapter(context);
}
@Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (getChildCount() > 0) {
+ View firstChild = getChildAt(0);
+ boolean isVisible = firstChild.getLocalVisibleRect(mFirstChildVisibleRect);
+ boolean clipped = mFirstChildVisibleRect.left > 0
+ || mFirstChildVisibleRect.right < firstChild.getWidth();
+ if (!isVisible || clipped) {
+ setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
+ return;
+ }
+ }
+
+ setGravity(Gravity.CENTER);
+ }
+
+ @Override
public void append() {
int size = getResources().getDimensionPixelSize(R.dimen.password_shape_size);
ImageView pinDot = new ImageView(getContext());
diff --git a/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
new file mode 100644
index 0000000..f06cb41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.Context
+
+class PhoneSystemUIAppComponentFactory : SystemUIAppComponentFactoryBase() {
+ override fun createSystemUIInitializer(context: Context) = SystemUIInitializerImpl(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
deleted file mode 100644
index 527ce12..0000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.content.Context;
-
-/**
- * Starts up SystemUI using the AOSP {@link SystemUIInitializerImpl}.
- *
- * This initializer relies on reflection to start everything up and should be considered deprecated.
- * Instead, create your own {@link SystemUIAppComponentFactoryBase}, specify it in your
- * AndroidManifest.xml and construct your own {@link SystemUIInitializer} directly.
- *
- * @deprecated Define your own SystemUIAppComponentFactoryBase implementation and use that. This
- * implementation may be changed or removed in future releases.
- */
-@Deprecated
-public class SystemUIAppComponentFactory extends SystemUIAppComponentFactoryBase {
- @Override
- protected SystemUIInitializer createSystemUIInitializer(Context context) {
- return SystemUIInitializerFactory.createWithContext(context);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 5505267..522b397 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,7 +54,7 @@
* Application class for SystemUI.
*/
public class SystemUIApplication extends Application implements
- SystemUIAppComponentFactory.ContextInitializer {
+ SystemUIAppComponentFactoryBase.ContextInitializer {
public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
@@ -66,7 +66,7 @@
*/
private CoreStartable[] mServices;
private boolean mServicesStarted;
- private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+ private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
private SysUIComponent mSysUIComponent;
private SystemUIInitializer mInitializer;
@@ -366,7 +366,7 @@
@Override
public void setContextAvailableCallback(
- SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+ SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
deleted file mode 100644
index b9454e8..0000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
+++ /dev/null
@@ -1,72 +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
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.util.Log
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.util.Assert
-
-/**
- * Factory to reflectively lookup a [SystemUIInitializer] to start SystemUI with.
- */
-@Deprecated("Provide your own {@link SystemUIAppComponentFactoryBase} that doesn't need this.")
-object SystemUIInitializerFactory {
- private const val TAG = "SysUIInitializerFactory"
- @SuppressLint("StaticFieldLeak")
- private var initializer: SystemUIInitializer? = null
-
- /**
- * Instantiate a [SystemUIInitializer] reflectively.
- */
- @JvmStatic
- fun createWithContext(context: Context): SystemUIInitializer {
- return createFromConfig(context)
- }
-
- /**
- * Instantiate a [SystemUIInitializer] reflectively.
- */
- @JvmStatic
- private fun createFromConfig(context: Context): SystemUIInitializer {
- Assert.isMainThread()
-
- return createFromConfigNoAssert(context)
- }
-
- @JvmStatic
- @VisibleForTesting
- fun createFromConfigNoAssert(context: Context): SystemUIInitializer {
-
- return initializer ?: run {
- val className = context.getString(R.string.config_systemUIFactoryComponent)
- if (className.isEmpty()) {
- throw RuntimeException("No SystemUIFactory component configured")
- }
- try {
- val cls = context.classLoader.loadClass(className)
- val constructor = cls.getConstructor(Context::class.java)
- (constructor.newInstance(context) as SystemUIInitializer).apply {
- initializer = this
- }
- } catch (t: Throwable) {
- Log.w(TAG, "Error creating SystemUIInitializer component: $className", t)
- throw t
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index b086912..31b0f056 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -18,7 +18,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -405,16 +404,23 @@
}
private int getMagnificationMode() {
+ // If current capability is window mode, we would like the default value of the mode to
+ // be WINDOW, otherwise, the default value would be FULLSCREEN.
+ int defaultValue =
+ (getMagnificationCapability() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
+ ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+
return mSecureSettings.getIntForUser(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
- ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+ defaultValue,
UserHandle.USER_CURRENT);
}
private int getMagnificationCapability() {
return mSecureSettings.getIntForUser(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
- ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+ ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
UserHandle.USER_CURRENT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 665a398..2b83e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -105,6 +105,8 @@
AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS =
AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS;
+ public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS =
+ AssistUtils.INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS;
public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1;
public static final int DISMISS_REASON_TAP = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d976ead1..e58876a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -242,15 +242,17 @@
}
private boolean isOwnerInForeground() {
- final String clientPackage = mCurrentDialog.getOpPackageName();
- final List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (!topPackage.contentEquals(clientPackage)
- && !Utils.isSystem(mContext, clientPackage)) {
- Log.w(TAG, "Evicting client due to: " + topPackage);
- return false;
+ if (mCurrentDialog != null) {
+ final String clientPackage = mCurrentDialog.getOpPackageName();
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(clientPackage)
+ && !Utils.isSystem(mContext, clientPackage)) {
+ Log.w(TAG, "Evicting client due to: " + topPackage);
+ return false;
+ }
}
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b89f481..a91499a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -173,6 +173,7 @@
@NonNull private final SecureSettings mSecureSettings;
@NonNull private final UdfpsUtils mUdfpsUtils;
@NonNull private final InputManager mInputManager;
+ @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
private final boolean mIgnoreRefreshRate;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -272,7 +273,8 @@
mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
- mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils)));
+ mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
+ mUdfpsKeyguardAccessibilityDelegate)));
}
@Override
@@ -825,7 +827,8 @@
@NonNull SecureSettings secureSettings,
@NonNull InputManager inputManager,
@NonNull UdfpsUtils udfpsUtils,
- @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
+ @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+ @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -871,6 +874,7 @@
mSecureSettings = secureSettings;
mUdfpsUtils = udfpsUtils;
mInputManager = inputManager;
+ mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
? singlePointerTouchProcessor : null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 195e5b9..e542147 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -103,7 +103,8 @@
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
- private val udfpsUtils: UdfpsUtils
+ private val udfpsUtils: UdfpsUtils,
+ private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -261,6 +262,7 @@
featureFlags,
primaryBouncerInteractor,
alternateBouncerInteractor,
+ udfpsKeyguardAccessibilityDelegate,
)
}
REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
new file mode 100644
index 0000000..fb7b56e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+@SysUISingleton
+class UdfpsKeyguardAccessibilityDelegate
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val keyguardViewManager: StatusBarKeyguardViewManager,
+) : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val clickAction =
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ resources.getString(R.string.accessibility_bouncer)
+ )
+ info.addAction(clickAction)
+ }
+
+ override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ // when an a11y service is enabled, double tapping on the fingerprint sensor should
+ // show the primary bouncer
+ return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
+ keyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+ true
+ } else super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 0bb4442..9bafeec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -20,6 +20,7 @@
import android.content.res.Configuration
import android.util.MathUtils
import android.view.MotionEvent
+import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -71,6 +72,7 @@
featureFlags: FeatureFlags,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
@@ -300,7 +302,10 @@
lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
view.mUseExpandedOverlay = useExpandedOverlay
- view.startIconAsyncInflate()
+ view.startIconAsyncInflate {
+ (view.findViewById(R.id.udfps_animation_view_internal) as View).accessibilityDelegate =
+ udfpsKeyguardAccessibilityDelegate
+ }
}
override fun onViewDetached() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 056d692..b916810c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -79,6 +79,7 @@
private float mInterpolatedDarkAmount;
private int mAnimationType = ANIMATION_NONE;
private boolean mFullyInflated;
+ private Runnable mOnFinishInflateRunnable;
public UdfpsKeyguardViewLegacy(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -90,7 +91,12 @@
.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
}
- public void startIconAsyncInflate() {
+ /**
+ * Inflate internal udfps view on a background thread and call the onFinishRunnable
+ * when inflation is finished.
+ */
+ public void startIconAsyncInflate(Runnable onFinishInflate) {
+ mOnFinishInflateRunnable = onFinishInflate;
// inflate Lottie views on a background thread in case it takes a while to inflate
AsyncLayoutInflater inflater = new AsyncLayoutInflater(mContext);
inflater.inflate(R.layout.udfps_keyguard_view_internal, this,
@@ -330,6 +336,7 @@
frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
PorterDuff.Mode.SRC_ATOP)
);
+ mOnFinishInflateRunnable.run();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 3ec8050..34c8d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -339,7 +339,13 @@
launch {
delay(authState.delay)
- legacyCallback.onAction(Callback.ACTION_AUTHENTICATED)
+ legacyCallback.onAction(
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ Callback.ACTION_AUTHENTICATED_AND_CONFIRMED
+ } else {
+ Callback.ACTION_AUTHENTICATED
+ }
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1dffa80..1a286cf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -28,6 +28,7 @@
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
+import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import com.android.systemui.R
import com.android.systemui.biometrics.AuthDialog
@@ -78,9 +79,11 @@
// cache the original position of the icon view (as done in legacy view)
// this must happen before any size changes can be made
- var iconHolderOriginalY = 0f
view.doOnLayout {
- iconHolderOriginalY = iconHolderView.y
+ // TODO(b/251476085): this old way of positioning has proven itself unreliable
+ // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+ // pin to the physical sensor
+ val iconHolderOriginalY = iconHolderView.y
// bind to prompt
// TODO(b/251476085): migrate the legacy panel controller and simplify this
@@ -141,7 +144,11 @@
listOf(
iconHolderView.asVerticalAnimator(
duration = duration.toLong(),
- toY = iconHolderOriginalY,
+ toY =
+ iconHolderOriginalY -
+ viewsToHideWhenSmall
+ .filter { it.isGone }
+ .sumOf { it.height },
),
viewsToFadeInOnSizeChange.asFadeInAnimator(
duration = duration.toLong(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
index 9cb91b3..444082c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -29,10 +29,16 @@
val needsUserConfirmation: Boolean = false,
val delay: Long = 0,
) {
+ private var wasConfirmed = false
+
/** If authentication was successful and the user has confirmed (or does not need to). */
val isAuthenticatedAndConfirmed: Boolean
get() = isAuthenticated && !needsUserConfirmation
+ /** Same as [isAuthenticatedAndConfirmed] but only true if the user clicked a confirm button. */
+ val isAuthenticatedAndExplicitlyConfirmed: Boolean
+ get() = isAuthenticated && wasConfirmed
+
/** If a successful authentication has not occurred. */
val isNotAuthenticated: Boolean
get() = !isAuthenticated
@@ -45,12 +51,16 @@
val isAuthenticatedByFingerprint: Boolean
get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint
- /** Copies this state, but toggles [needsUserConfirmation] to false. */
- fun asConfirmed(): PromptAuthState =
+ /**
+ * Copies this state, but toggles [needsUserConfirmation] to false and ensures that
+ * [isAuthenticatedAndExplicitlyConfirmed] is true.
+ */
+ fun asExplicitlyConfirmed(): PromptAuthState =
PromptAuthState(
- isAuthenticated = isAuthenticated,
- authenticatedModality = authenticatedModality,
- needsUserConfirmation = false,
- delay = delay,
- )
+ isAuthenticated = isAuthenticated,
+ authenticatedModality = authenticatedModality,
+ needsUserConfirmation = false,
+ delay = delay,
+ )
+ .apply { wasConfirmed = true }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2f8ed09..0fffee6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -414,7 +414,7 @@
return
}
- _isAuthenticated.value = authState.asConfirmed()
+ _isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
_legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index e1545a4..7c90735 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -391,7 +391,8 @@
private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
val canRunFaceAuth =
keyguardStateController.isFaceAuthEnabled &&
- keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
+ keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()
val canRunActiveUnlock =
currentUserActiveUnlockRunning &&
keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b71871e..d70c57f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,12 +16,11 @@
package com.android.systemui.dagger;
-import android.content.Context;
import android.os.HandlerThread;
import androidx.annotation.Nullable;
-import com.android.systemui.SystemUIInitializerFactory;
+import com.android.systemui.SystemUIInitializer;
import com.android.systemui.tv.TvWMComponent;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
@@ -49,7 +48,7 @@
/**
* Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
* from the WM component into the SysUI component (in
- * {@link SystemUIInitializerFactory#init(Context, boolean)}), and references the specific dependencies
+ * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies
* provided by its particular device/form-factor SystemUI implementation.
*
* ie. {@link WMComponent} includes {@link WMShellModule}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index c5e7e0d..ee046c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -34,13 +34,11 @@
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_ANIMATION_DURATION
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
-import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
@@ -129,6 +127,12 @@
)
}
}
+
+ launch {
+ transitionViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
+ }
}
configController.removeCallback(configCallback)
@@ -251,9 +255,9 @@
}
/** Starts the dream content and dream overlay exit animations. */
- fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
+ fun wakeUp() {
cancelAnimations()
- executor.executeDelayed(doneCallback, DREAM_ANIMATION_DURATION.inWholeMilliseconds)
+ mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 94523df..78ac453 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -31,8 +31,6 @@
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.NonNull;
-
import com.android.app.animation.Interpolators;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R;
@@ -46,7 +44,6 @@
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.util.ViewController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Arrays;
@@ -302,20 +299,15 @@
/**
* Handle the dream waking up and run any necessary animations.
- *
- * @param onAnimationEnd Callback to trigger once animations are finished.
- * @param callbackExecutor Executor to execute the callback on.
*/
- public void wakeUp(@NonNull Runnable onAnimationEnd,
- @NonNull DelayableExecutor callbackExecutor) {
+ public void wakeUp() {
// When swiping causes wakeup, do not run any animations as the dream should exit as soon
// as possible.
if (mWakingUpFromSwipe) {
- onAnimationEnd.run();
return;
}
- mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
+ mDreamOverlayAnimationsController.wakeUp();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index df2a749..553405f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -116,6 +116,17 @@
}
};
+ private final DreamOverlayStateController.Callback mExitAnimationFinishedCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ if (!mStateController.areExitAnimationsRunning()) {
+ mStateController.removeCallback(mExitAnimationFinishedCallback);
+ resetCurrentDreamOverlayLocked();
+ }
+ }
+ };
+
private final DreamOverlayStateController mStateController;
@VisibleForTesting
@@ -257,10 +268,10 @@
}
@Override
- public void onWakeUp(@NonNull Runnable onCompletedCallback) {
+ public void onWakeUp() {
if (mDreamOverlayContainerViewController != null) {
mDreamOverlayCallbackController.onWakeUp();
- mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+ mDreamOverlayContainerViewController.wakeUp();
}
}
@@ -330,6 +341,11 @@
}
private void resetCurrentDreamOverlayLocked() {
+ if (mStateController.areExitAnimationsRunning()) {
+ mStateController.addCallback(mExitAnimationFinishedCallback);
+ return;
+ }
+
if (mStarted && mWindow != null) {
try {
mWindowManager.removeView(mWindow.getDecorView());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index c6b6056..1cd3774 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -35,9 +35,11 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dreams.touch.scrim.ScrimController;
import com.android.systemui.dreams.touch.scrim.ScrimManager;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -76,6 +78,8 @@
private static final String TAG = "BouncerSwipeTouchHandler";
private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final LockPatternUtils mLockPatternUtils;
+ private final UserTracker mUserTracker;
private final float mBouncerZoneScreenPercentage;
private final ScrimManager mScrimManager;
@@ -151,6 +155,11 @@
return true;
}
+ // Don't set expansion if the user doesn't have a pin/password set.
+ if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+ return true;
+ }
+
// 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
@@ -204,6 +213,8 @@
NotificationShadeWindowController notificationShadeWindowController,
ValueAnimatorCreator valueAnimatorCreator,
VelocityTrackerFactory velocityTrackerFactory,
+ LockPatternUtils lockPatternUtils,
+ UserTracker userTracker,
@Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
FlingAnimationUtils flingAnimationUtils,
@Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
@@ -213,6 +224,8 @@
mCentralSurfaces = centralSurfaces;
mScrimManager = scrimManager;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mLockPatternUtils = lockPatternUtils;
+ mUserTracker = userTracker;
mBouncerZoneScreenPercentage = swipeRegionPercentage;
mFlingAnimationUtils = flingAnimationUtils;
mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
@@ -347,6 +360,11 @@
return;
}
+ // Don't set expansion if the user doesn't have a pin/password set.
+ if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+ return;
+ }
+
// The animation utils deal in pixel units, rather than expansion height.
final float viewHeight = mTouchSession.getBounds().height();
final float currentHeight = viewHeight * mCurrentExpansion;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ea51915..634a7e3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -247,7 +247,7 @@
/** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
// TODO(b/279794160): Tracking bug.
@JvmField
- val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+ val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer", teamfood = true)
/** Migrate the indication area to the new keyguard root view. */
// TODO(b/280067944): Tracking bug.
@@ -438,9 +438,6 @@
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
- // TODO(b/265045965): Tracking Bug
- val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
-
// TODO(b/273509374): Tracking Bug
@JvmField
val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag(1006,
@@ -657,7 +654,7 @@
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
@JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
// TODO(b/278622168): Tracking Bug
- @JvmField val BIOMETRIC_BP_STRONG = unreleasedFlag(2202, "biometric_bp_strong")
+ @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
// 2300 - stylus
@JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
@@ -714,11 +711,6 @@
val KEYBOARD_EDUCATION =
unreleasedFlag(2603, "keyboard_education", teamfood = false)
- // TODO(b/272036292): Tracking Bug
- @JvmField
- val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
- releasedFlag(2602, "large_shade_granular_alpha_interpolation")
-
// TODO(b/277201412): Tracking Bug
@JvmField
val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 573de97..0511314 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -49,7 +49,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
-import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -72,13 +72,13 @@
/**
* Simple Slice provider that shows the current date.
*
- * Injection is handled by {@link SystemUIAppComponentFactory} +
+ * Injection is handled by {@link SystemUIAppComponentFactoryBase} +
* {@link com.android.systemui.dagger.GlobalRootComponent#inject(KeyguardSliceProvider)}.
*/
public class KeyguardSliceProvider extends SliceProvider implements
NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
- SystemUIAppComponentFactory.ContextInitializer {
+ SystemUIAppComponentFactoryBase.ContextInitializer {
private static final String TAG = "KgdSliceProvider";
@@ -148,7 +148,7 @@
protected boolean mDozing;
private int mStatusBarState;
private boolean mMediaIsVisible;
- private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+ private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
@Inject
WakeLockLogger mWakeLockLogger;
@@ -533,7 +533,7 @@
@Override
public void setContextAvailableCallback(
- SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+ SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 835a491..049aa0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -20,6 +20,7 @@
import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
@@ -36,8 +37,8 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_DURATION_MS;
import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -88,9 +89,11 @@
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl.Transaction;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
@@ -128,12 +131,14 @@
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -166,6 +171,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+import kotlinx.coroutines.CoroutineDispatcher;
/**
* Mediates requests related to the keyguard. This includes queries about the
@@ -442,11 +450,6 @@
private final int mDreamOpenAnimationDuration;
/**
- * The duration in milliseconds of the dream close animation.
- */
- private final int mDreamCloseAnimationDuration;
-
- /**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
*/
@@ -704,6 +707,9 @@
}
}
break;
+ case TelephonyManager.SIM_STATE_UNKNOWN:
+ mPendingPinLock = false;
+ break;
default:
if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState);
break;
@@ -1132,49 +1138,57 @@
return;
}
- final RemoteAnimationTarget primary = apps[0];
+ mRemoteAnimationTarget = apps[0];
final boolean isDream = (apps[0].taskInfo != null
&& apps[0].taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
- final SyncRtSurfaceTransactionApplier applier =
- new SyncRtSurfaceTransactionApplier(
- mKeyguardViewControllerLazy.get().getViewRootImpl().getView());
+ final View localView = mKeyguardViewControllerLazy.get()
+ .getViewRootImpl().getView();
+ final SyncRtSurfaceTransactionApplier applier =
+ new SyncRtSurfaceTransactionApplier(localView);
mContext.getMainExecutor().execute(() -> {
if (mUnoccludeAnimator != null) {
mUnoccludeAnimator.cancel();
}
+ if (isDream) {
+ initAlphaForAnimationTargets(wallpapers);
+ getRemoteSurfaceAlphaApplier().accept(0.0f);
+ mDreamingToLockscreenTransitionViewModel.get()
+ .startTransition();
+ return;
+ }
+
mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
- mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
- : UNOCCLUDE_ANIMATION_DURATION);
+ mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
mUnoccludeAnimator.addUpdateListener(
animation -> {
final float animatedValue =
(float) animation.getAnimatedValue();
- final float surfaceHeight = primary.screenSpaceBounds.height();
+ final float surfaceHeight =
+ mRemoteAnimationTarget.screenSpaceBounds.height();
// Fade for all types of activities.
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
paramsBuilder =
new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(primary.leash)
+ .Builder(mRemoteAnimationTarget.leash)
.withAlpha(animatedValue);
- // Set translate if the occluding activity isn't Dream.
- if (!isDream) {
- mUnoccludeMatrix.setTranslate(
- 0f,
- (1f - animatedValue)
- * surfaceHeight
- * UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT);
- paramsBuilder.withMatrix(mUnoccludeMatrix).withCornerRadius(
- mWindowCornerRadius);
- }
+ mUnoccludeMatrix.setTranslate(
+ 0f,
+ (1f - animatedValue)
+ * surfaceHeight
+ * UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT);
+
+ paramsBuilder.withMatrix(mUnoccludeMatrix).withCornerRadius(
+ mWindowCornerRadius);
+
applier.scheduleApply(paramsBuilder.build());
});
mUnoccludeAnimator.addListener(new AnimatorListenerAdapter() {
@@ -1196,6 +1210,34 @@
}
};
+ private static void initAlphaForAnimationTargets(
+ @android.annotation.NonNull RemoteAnimationTarget[] targets
+ ) {
+ for (RemoteAnimationTarget target : targets) {
+ if (target.mode != MODE_OPENING) continue;
+
+ try (Transaction t = new Transaction()) {
+ t.setAlpha(target.leash, 1.f);
+ t.apply();
+ }
+ }
+ }
+
+ private Consumer<Float> getRemoteSurfaceAlphaApplier() {
+ return (Float alpha) -> {
+ if (mRemoteAnimationTarget == null) return;
+ final View localView = mKeyguardViewControllerLazy.get().getViewRootImpl().getView();
+ final SyncRtSurfaceTransactionApplier applier =
+ new SyncRtSurfaceTransactionApplier(localView);
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mRemoteAnimationTarget.leash)
+ .withAlpha(alpha).build();
+ applier.scheduleApply(params);
+ };
+ }
+
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
@@ -1226,6 +1268,10 @@
private FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
+ private final CoroutineDispatcher mMainDispatcher;
+ private final Lazy<DreamingToLockscreenTransitionViewModel>
+ mDreamingToLockscreenTransitionViewModel;
+ private RemoteAnimationTarget mRemoteAnimationTarget;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -1265,7 +1311,9 @@
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
- SystemClock systemClock) {
+ SystemClock systemClock,
+ @Main CoroutineDispatcher mainDispatcher,
+ Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1324,11 +1372,13 @@
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
- mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
+
+ mMainDispatcher = mainDispatcher;
+ mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
}
public void userActivity() {
@@ -1447,6 +1497,13 @@
mUpdateMonitor.registerCallback(mUpdateCallback);
adjustStatusBarLocked();
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
+
+ ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
+ if (viewRootImpl != null) {
+ collectFlow(viewRootImpl.getView(),
+ mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+ getRemoteSurfaceAlphaApplier(), mMainDispatcher);
+ }
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
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 ab1341e..5622df1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -38,6 +38,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
@@ -52,6 +53,7 @@
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
@@ -76,6 +78,8 @@
import java.util.concurrent.Executor;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Dagger Module providing keyguard.
*/
@@ -136,7 +140,9 @@
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
- SystemClock systemClock) {
+ SystemClock systemClock,
+ @Main CoroutineDispatcher mainDispatcher,
+ Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -174,7 +180,9 @@
featureFlags,
secureSettings,
systemSettings,
- systemClock);
+ systemClock,
+ mainDispatcher,
+ dreamingToLockscreenTransitionViewModel);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index c94aa11..84cd3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -165,35 +165,28 @@
// An animator was provided, so use it to run the transition
animator.setFloatValues(startingValue, 1f)
animator.duration = ((1f - startingValue) * animator.duration).toLong()
- val updateListener =
- object : AnimatorUpdateListener {
- override fun onAnimationUpdate(animation: ValueAnimator) {
- emitTransition(
- TransitionStep(
- info,
- (animation.getAnimatedValue() as Float),
- TransitionState.RUNNING
- )
- )
- }
- }
+ val updateListener = AnimatorUpdateListener { animation ->
+ emitTransition(
+ TransitionStep(
+ info,
+ (animation.animatedValue as Float),
+ TransitionState.RUNNING
+ )
+ )
+ }
val adapter =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
}
override fun onAnimationCancel(animation: Animator) {
- endAnimation(animation, lastStep.value, TransitionState.CANCELED)
+ endAnimation(lastStep.value, TransitionState.CANCELED)
}
override fun onAnimationEnd(animation: Animator) {
- endAnimation(animation, 1f, TransitionState.FINISHED)
+ endAnimation(1f, TransitionState.FINISHED)
}
- private fun endAnimation(
- animation: Animator,
- value: Float,
- state: TransitionState
- ) {
+ private fun endAnimation(value: Float, state: TransitionState) {
emitTransition(TransitionStep(info, value, state))
animator.removeListener(this)
animator.removeUpdateListener(updateListener)
@@ -206,7 +199,7 @@
return@startTransition null
}
?: run {
- emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
+ emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
// No animator, so it's manual. Provide a mechanism to callback
updateTransitionId = UUID.randomUUID()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 323fc31..ee2c2df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -52,7 +52,7 @@
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { (wakefulnessModel, lastStartedTransition) ->
if (
- wakefulnessModel.isStartingToWake() &&
+ wakefulnessModel.isStartingToWakeOrAwake() &&
lastStartedTransition.to == KeyguardState.DOZING
) {
keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 36c8eb1..ccf4bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,7 +23,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
@@ -48,39 +47,23 @@
) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
override fun start() {
- listenForDreamingToLockscreen()
listenForDreamingToOccluded()
listenForDreamingToGone()
listenForDreamingToDozing()
}
- private fun listenForDreamingToLockscreen() {
+ fun startToLockscreenTransition() {
scope.launch {
- keyguardInteractor.isAbleToDream
- .sample(
- combine(
- keyguardInteractor.dozeTransitionModel,
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
+ if (keyguardTransitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(TO_LOCKSCREEN_DURATION),
+ )
)
- .collect { (isDreaming, dozeTransitionModel, lastStartedTransition) ->
- if (
- !isDreaming &&
- isDozeOff(dozeTransitionModel.to) &&
- lastStartedTransition.to == KeyguardState.DREAMING
- ) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.DREAMING,
- KeyguardState.LOCKSCREEN,
- getAnimator(TO_LOCKSCREEN_DURATION),
- )
- )
- }
- }
+ }
}
}
@@ -173,6 +156,6 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
- val TO_LOCKSCREEN_DURATION = 1183.milliseconds
+ val TO_LOCKSCREEN_DURATION = 1167.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index a499e3d..228290a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -114,7 +114,7 @@
isDreaming && isDozeOff(dozeTransitionModel.to)
}
.sample(wakefulnessModel) { isAbleToDream, wakefulnessModel ->
- isAbleToDream && wakefulnessModel.isStartingToWake()
+ isAbleToDream && wakefulnessModel.isStartingToWakeOrAwake()
}
.flatMapLatest { isAbleToDream ->
flow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index da0ada1..42f12f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -29,10 +30,14 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to the keyguard transitions. */
@SysUISingleton
@@ -40,6 +45,7 @@
@Inject
constructor(
private val repository: KeyguardTransitionRepository,
+ @Application val scope: CoroutineScope,
) {
/** (any)->GONE transition information */
val anyStateToGoneTransition: Flow<TransitionStep> =
@@ -108,10 +114,17 @@
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
- /** The last completed [KeyguardState] transition */
- val finishedKeyguardState: Flow<KeyguardState> =
- finishedKeyguardTransitionStep.map { step -> step.to }
+ /** The destination state of the last started transition */
+ val startedKeyguardState: StateFlow<KeyguardState> =
+ startedKeyguardTransitionStep
+ .map { step -> step.to }
+ .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+ /** The last completed [KeyguardState] transition */
+ val finishedKeyguardState: StateFlow<KeyguardState> =
+ finishedKeyguardTransitionStep
+ .map { step -> step.to }
+ .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
/**
* The amount of transition into or out of the given [KeyguardState].
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index dd57713..cfd9e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -33,6 +33,8 @@
fun isDeviceInteractive() = !isAsleep()
+ fun isStartingToWakeOrAwake() = isStartingToWake() || state == WakefulnessState.AWAKE
+
fun isStartingToSleepFromPowerButton() =
isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 2c9a9b3..9ca4bd6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -16,15 +16,17 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -34,22 +36,32 @@
class DreamingToLockscreenTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
) {
+ fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition()
+
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_LOCKSCREEN_DURATION,
- transitionFlow = interactor.dreamingToLockscreenTransition,
+ transitionFlow = keyguardTransitionInteractor.dreamingToLockscreenTransition,
)
+ val transitionEnded =
+ keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step ->
+ step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED
+ }
+
/** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
return transitionAnimation.createFlow(
- duration = 600.milliseconds,
+ duration = TO_LOCKSCREEN_DURATION,
onStep = { it * translatePx },
- interpolator = EMPHASIZED_ACCELERATE,
+ interpolator = EMPHASIZED,
)
}
+
/** Dream overlay views alpha - fade out */
val dreamOverlayAlpha: Flow<Float> =
transitionAnimation.createFlow(
@@ -65,7 +77,7 @@
// Reset on cancel or finish
onFinish = { 0f },
onCancel = { 0f },
- interpolator = EMPHASIZED_DECELERATE,
+ interpolator = EMPHASIZED,
)
}
@@ -76,12 +88,4 @@
duration = 250.milliseconds,
onStep = { it },
)
-
- companion object {
- /* Length of time before ending the dream activity, in order to start unoccluding */
- val DREAM_ANIMATION_DURATION = 250.milliseconds
- @JvmField
- val LOCKSCREEN_ANIMATION_DURATION_MS =
- (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index c6187dd..a3ae67d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -33,7 +33,7 @@
class LockscreenToDreamingTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
+ interactor: KeyguardTransitionInteractor,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 14386c1..0819d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -248,9 +248,9 @@
private final FeatureFlags mFeatureFlags;
private final GlobalSettings mGlobalSettings;
- // TODO(b/281032715): Consider making this as a final variable. For now having a null check
- // due to unit test failure. (Perhaps missing some setup)
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
+ private boolean mWasPlaying = false;
+ private boolean mButtonClicked = false;
private ContentObserver mAnimationScaleObserver = new ContentObserver(null) {
@Override
@@ -582,6 +582,25 @@
if (!mMetadataAnimationHandler.isRunning()) {
mMediaViewController.refreshState();
}
+
+ // Turbulence noise
+ if (shouldPlayTurbulenceNoise()) {
+ if (mTurbulenceNoiseAnimationConfig == null) {
+ mTurbulenceNoiseAnimationConfig =
+ createTurbulenceNoiseAnimation();
+ }
+ // Color will be correctly updated in ColorSchemeTransition.
+ mTurbulenceNoiseController.play(
+ mTurbulenceNoiseAnimationConfig
+ );
+ mMainExecutor.executeDelayed(
+ mTurbulenceNoiseController::finish,
+ TURBULENCE_NOISE_PLAY_DURATION
+ );
+ }
+ mButtonClicked = false;
+ mWasPlaying = isPlaying();
+
Trace.endSection();
}
@@ -1155,21 +1174,14 @@
if (!mFalsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
+ // Used to determine whether to play turbulence noise.
+ mWasPlaying = isPlaying();
+ mButtonClicked = true;
+
action.run();
+
if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) {
mMultiRippleController.play(createTouchRippleAnimation(button));
- if (mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE)) {
- if (mTurbulenceNoiseAnimationConfig == null) {
- mTurbulenceNoiseAnimationConfig =
- createTurbulenceNoiseAnimation();
- }
- // Color will be correctly updated in ColorSchemeTransition.
- mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
- mMainExecutor.executeDelayed(
- mTurbulenceNoiseController::finish,
- TURBULENCE_NOISE_PLAY_DURATION
- );
- }
}
if (icon instanceof Animatable) {
@@ -1208,6 +1220,11 @@
);
}
+ private boolean shouldPlayTurbulenceNoise() {
+ return mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE) && mButtonClicked && !mWasPlaying
+ && isPlaying();
+ }
+
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
return new TurbulenceNoiseAnimationConfig(
/* gridCount= */ 2.14f,
@@ -1218,12 +1235,12 @@
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* backgroundColor= */ Color.BLACK,
/* opacity= */ 51,
- /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
- /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+ /* width= */ mMediaViewHolder.getTurbulenceNoiseView().getWidth(),
+ /* height= */ mMediaViewHolder.getTurbulenceNoiseView().getHeight(),
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
/* easeInDuration= */ 1350f,
/* easeOutDuration= */ 1350f,
- this.getContext().getResources().getDisplayMetrics().density,
+ getContext().getResources().getDisplayMetrics().density,
BlendMode.SCREEN,
/* onAnimationEnd= */ null,
/* lumaMatteBlendFactor= */ 0.26f,
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 22679c7..5e9406c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1136,7 +1136,8 @@
private boolean isButtonPressFromTrackpad(MotionEvent ev) {
// We don't allow back for button press from the trackpad, and yet we do with a mouse.
int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
- return (sources & (SOURCE_MOUSE | SOURCE_TOUCHPAD)) == sources && ev.getButtonState() != 0;
+ int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD);
+ return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0;
}
private void dispatchToBackAnimation(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 06e92d9..a2b2a89 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,7 +52,6 @@
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
@@ -755,8 +754,7 @@
// Alpha progress should be linear on lockscreen shade expansion.
return progress;
}
- if (mIsSmallScreen || !mFeatureFlags.isEnabled(
- Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (mIsSmallScreen) {
return ShadeInterpolation.getContentAlpha(progress);
} else {
return mLargeScreenShadeInterpolator.getQsAlpha(progress);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index eef4c1d..b5e6a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -166,17 +166,6 @@
}
@Override
- protected void handleLongClick(@Nullable View view) {
- try {
- // Need to wake on long click so bouncer->settings works.
- mDreamManager.awaken();
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Can't awaken", e);
- }
- super.handleLongClick(view);
- }
-
- @Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.label = getTileLabel();
state.secondaryLabel = getActiveDreamName();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b1dadf0..7294137 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2906,9 +2906,7 @@
float scrimMinFraction;
if (mSplitShadeEnabled) {
boolean highHun = mHeadsUpStartHeight * 2.5
- >
- (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
- ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
+ > mSplitShadeFullTransitionDistance;
// if HUN height is higher than 40% of predefined transition distance, it means HUN
// is too high for regular transition. In that case we need to calculate transition
// distance - here we take scrim transition distance as equal to shade transition
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 4e1c272..abb69f6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -16,24 +16,11 @@
package com.android.systemui.shade.transition
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.util.MathUtils.constrain
-import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.shade.PanelState
-import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.util.LargeScreenUtils
import java.io.PrintWriter
import javax.inject.Inject
@@ -42,37 +29,19 @@
class ScrimShadeTransitionController
@Inject
constructor(
- configurationController: ConfigurationController,
dumpManager: DumpManager,
private val scrimController: ScrimController,
- @Main private val resources: Resources,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val headsUpManager: HeadsUpManager,
- private val featureFlags: FeatureFlags,
) {
- private var inSplitShade = false
- private var splitShadeScrimTransitionDistance = 0
private var lastExpansionFraction: Float? = null
private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
private var currentPanelState: Int? = null
init {
- updateResources()
- configurationController.addCallback(
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
- }
- })
dumpManager.registerDumpable(
- ScrimShadeTransitionController::class.java.simpleName, this::dump)
- }
-
- private fun updateResources() {
- inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
- splitShadeScrimTransitionDistance =
- resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+ ScrimShadeTransitionController::class.java.simpleName,
+ this::dump
+ )
}
fun onPanelStateChanged(@PanelState state: Int) {
@@ -87,46 +56,25 @@
private fun onStateChanged() {
val expansionEvent = lastExpansionEvent ?: return
- val panelState = currentPanelState
- val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+ val expansionFraction = calculateScrimExpansionFraction(expansionEvent)
scrimController.setRawPanelExpansionFraction(expansionFraction)
lastExpansionFraction = expansionFraction
}
- private fun calculateScrimExpansionFraction(
- expansionEvent: ShadeExpansionChangeEvent,
- @PanelState panelState: Int?
- ): Float {
- return if (canUseCustomFraction(panelState)) {
- constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
- } else {
- expansionEvent.fraction
- }
+ private fun calculateScrimExpansionFraction(expansionEvent: ShadeExpansionChangeEvent): Float {
+ return expansionEvent.fraction
}
- private fun canUseCustomFraction(panelState: Int?) =
- inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING &&
- // in case of HUN we can't always use predefined distances to manage scrim
- // transition because dragDownPxAmount can start from value bigger than
- // splitShadeScrimTransitionDistance
- !headsUpManager.isTrackingHeadsUp &&
- !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
-
- private fun isScreenUnlocked() =
- statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
-
private fun dump(printWriter: PrintWriter, args: Array<String>) {
printWriter.println(
"""
ScrimShadeTransitionController:
- Resources:
- inSplitShade: $inSplitShade
- isScreenUnlocked: ${isScreenUnlocked()}
- splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
State:
currentPanelState: $currentPanelState
lastExpansionFraction: $lastExpansionFraction
lastExpansionEvent: $lastExpansionEvent
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9d7f3be..edf37ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -40,8 +40,6 @@
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -226,9 +224,7 @@
if (ambientState.isBouncerInTransit()) {
viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- FeatureFlags flags = ambientState.getFeatureFlags();
- if (ambientState.isSmallScreen() || !flags.isEnabled(
- Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (ambientState.isSmallScreen()) {
viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
} else {
LargeScreenShadeInterpolator interpolator =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 23b5241..314566e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -633,12 +633,17 @@
mFSIUpdateCandidates.removeAll(toRemoveForFSI)
}
- /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
+ /**
+ * When an action is pressed on a notification, make sure we don't lifetime-extend it in the
+ * future by informing the HeadsUpManager, and make sure we don't keep lifetime-extending it if
+ * we already are.
+ *
+ * @see HeadsUpManager.setUserActionMayIndirectlyRemove
+ * @see HeadsUpManager.canRemoveImmediately
+ */
private val mActionPressListener = Consumer<NotificationEntry> { entry ->
- if (mNotifsExtendingLifetime.contains(entry)) {
- val removeInMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
- mExecutor.executeDelayed({ endNotifLifetimeExtensionIfExtended(entry) }, removeInMillis)
- }
+ mHeadsUpManager.setUserActionMayIndirectlyRemove(entry)
+ mExecutor.execute { endNotifLifetimeExtensionIfExtended(entry) }
}
private val mLifetimeExtender = object : NotifLifetimeExtender {
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 6f1c378..ce2658d 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
@@ -29,8 +29,6 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -189,9 +187,7 @@
private float interpolateFooterAlpha(AmbientState ambientState) {
float expansion = ambientState.getExpansionFraction();
- FeatureFlags flags = ambientState.getFeatureFlags();
- if (ambientState.isSmallScreen()
- || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (ambientState.isSmallScreen()) {
return ShadeInterpolation.getContentAlpha(expansion);
}
LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
@@ -200,9 +196,7 @@
private float interpolateNotificationContentAlpha(AmbientState ambientState) {
float expansion = ambientState.getExpansionFraction();
- FeatureFlags flags = ambientState.getFeatureFlags();
- if (ambientState.isSmallScreen()
- || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (ambientState.isSmallScreen()) {
return ShadeInterpolation.getContentAlpha(expansion);
}
LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a42e360..47c4023 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -881,24 +881,11 @@
mBehindAlpha = 1;
mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
} else {
- if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
- mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
- mPanelExpansionFraction * mDefaultScrimAlpha);
- mNotificationsAlpha =
- mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
- mPanelExpansionFraction);
- } else {
- // Behind scrim will finish fading in at 30% expansion.
- float behindFraction = MathUtils
- .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
- mBehindAlpha = behindFraction * mDefaultScrimAlpha;
- // Delay fade-in of notification scrim a bit further, to coincide with the
- // behind scrim finishing fading in.
- // Also to coincide with the view starting to fade in, otherwise the empty
- // panel can be quite jarring.
- mNotificationsAlpha = MathUtils
- .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction);
- }
+ mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+ mPanelExpansionFraction * mDefaultScrimAlpha);
+ mNotificationsAlpha =
+ mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+ mPanelExpansionFraction);
}
mBehindTint = mState.getBehindTint();
mInFrontAlpha = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 23afd8b3..d472c35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -393,6 +393,31 @@
}
}
+ /**
+ * Notes that the user took an action on an entry that might indirectly cause the system or the
+ * app to remove the notification.
+ *
+ * @param entry the entry that might be indirectly removed by the user's action
+ *
+ * @see com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator#mActionPressListener
+ * @see #canRemoveImmediately(String)
+ */
+ public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+ if (headsUpEntry != null) {
+ headsUpEntry.userActionMayIndirectlyRemove = true;
+ }
+ }
+
+ @Override
+ public boolean canRemoveImmediately(@NonNull String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (headsUpEntry != null && headsUpEntry.userActionMayIndirectlyRemove) {
+ return true;
+ }
+ return super.canRemoveImmediately(key);
+ }
+
@NonNull
@Override
protected HeadsUpEntry createAlertEntry() {
@@ -421,6 +446,8 @@
*/
protected class HeadsUpEntry extends AlertEntry {
public boolean remoteInputActive;
+ public boolean userActionMayIndirectlyRemove;
+
protected boolean expanded;
protected boolean wasUnpinned;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 109c1cf..f37a9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -390,8 +390,6 @@
public void init(int windowType, Callback callback) {
initDialog(mActivityManager.getLockTaskModeState());
- mAccessibility.init();
-
mController.addCallback(mControllerCallbackH, mHandler);
mController.getState();
@@ -478,8 +476,7 @@
mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
@@ -677,6 +674,7 @@
initRingerH();
initSettingsH(lockTaskModeState);
initODICaptionsH();
+ mAccessibility.init();
}
private boolean isWindowGravityLeft() {
@@ -994,7 +992,8 @@
}
@VisibleForTesting String getSelectedRingerContainerDescription() {
- return mSelectedRingerContainer.getContentDescription().toString();
+ return mSelectedRingerContainer == null ? null :
+ mSelectedRingerContainer.getContentDescription().toString();
}
@VisibleForTesting void toggleRingerDrawer(boolean show) {
@@ -1138,7 +1137,7 @@
* @param open false to set the description when drawer is closed
*/
private void updateSelectedRingerContainerDescription(boolean open) {
- if (mState == null) return;
+ if (mState == null || mSelectedRingerContainer == null) return;
String currentMode = mContext.getString(getStringDescriptionResourceForRingerMode(
mState.ringerModeInternal));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index f5cd0ca..319a02d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -50,6 +50,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -59,6 +60,7 @@
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
+@Ignore("b/286245842")
public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 617b893..0dcd404 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -47,6 +47,7 @@
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -121,7 +122,7 @@
bouncerRepository = bouncerRepository,
configurationRepository = FakeConfigurationRepository(),
),
- KeyguardTransitionInteractor(repository = transitionRepository),
+ KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 419d045..0edfd77 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,6 +38,7 @@
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
+import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -134,6 +135,7 @@
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
@@ -277,6 +279,7 @@
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
private TestableLooper mTestableLooper;
+ private FakeFeatureFlags mFeatureFlags;
private Handler mHandler;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
private MockitoSession mMockitoSession;
@@ -325,6 +328,8 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
@@ -1430,6 +1435,23 @@
}
@Test
+ public void testOccludingAppFingerprintListeningState_featureFlagEnabled() {
+ mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+ // GIVEN keyguard isn't visible (app occluding)
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+ when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+
+ // THEN we SHOULD listen for non-UDFPS fingerprint
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(true);
+
+ // THEN we should listen for udfps (hiding of mechanism to actually auth is
+ // controlled by UdfpsKeyguardViewController)
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+ }
+
+ @Test
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -2800,6 +2822,16 @@
);
}
+ @Test
+ public void testOnSimStateChanged_Unknown() {
+ KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = spy(
+ KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
+ mKeyguardUpdateMonitor.handleSimStateChange(-1, 0, TelephonyManager.SIM_STATE_UNKNOWN);
+ verify(keyguardUpdateMonitorCallback).onSimStateChanged(-1, 0,
+ TelephonyManager.SIM_STATE_UNKNOWN);
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -3126,7 +3158,7 @@
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
mFaceWakeUpTriggersConfig, mDevicePostureController,
- Optional.of(mInteractiveToAuthProvider));
+ Optional.of(mInteractiveToAuthProvider), mFeatureFlags);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index bdf6bee..c88c4d6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -158,7 +158,8 @@
mVibrator,
mAuthRippleController,
mResources,
- new KeyguardTransitionInteractor(mTransitionRepository),
+ new KeyguardTransitionInteractor(mTransitionRepository,
+ TestScopeProvider.getTestScope().getBackgroundScope()),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags
);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
new file mode 100644
index 0000000..6c35734
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.keyguard
+import kotlinx.coroutines.test.TestScope
+class TestScopeProvider {
+ companion object {
+ @JvmStatic fun getTestScope() = TestScope()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index a0fdc8f..24a5e80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -48,8 +48,7 @@
@Test
public void testInitDependency() throws ExecutionException, InterruptedException {
Dependency.clearDependencies();
- SystemUIInitializer initializer =
- SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
initializer.init(true);
Dependency dependency = initializer.getSysUIComponent().createDependency();
dependency.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index b3f9958..275723b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -20,7 +20,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +28,7 @@
import static junit.framework.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -525,13 +525,13 @@
private void setupMagnificationCapabilityAndMode(int capability, int mode) {
when(mSecureSettings.getIntForUser(
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
- ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
- UserHandle.USER_CURRENT)).thenReturn(capability);
+ eq(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY),
+ anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(capability);
when(mSecureSettings.getIntForUser(
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
- ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
- UserHandle.USER_CURRENT)).thenReturn(mode);
+ eq(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE),
+ anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(mode);
}
private void setupScaleInSecureSettings(float scale) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index 57a355f..5e1a8e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -37,7 +37,7 @@
private fun assertSameAxes(expect: Font, actual: Font) {
val expectAxes = expect.axes?.also { it.sortBy { axis -> axis.tag } }
val actualAxes = actual.axes?.also { it.sortBy { axis -> axis.tag } }
- assertThat(expectAxes).isEqualTo(actualAxes)
+ assertThat(actualAxes).isEqualTo(expectAxes)
}
private fun assertSameAxes(expectVarSettings: String, actual: Font) {
@@ -46,7 +46,7 @@
it.sortBy { axis -> axis.tag }
}
val actualAxes = actual.axes?.also { it.sortBy { axis -> axis.tag } }
- assertThat(expectAxes).isEqualTo(actualAxes)
+ assertThat(actualAxes).isEqualTo(expectAxes)
}
@Test
@@ -61,7 +61,7 @@
val interp = FontInterpolator()
assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f))
assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f))
- assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
}
@Test
@@ -74,7 +74,7 @@
.build()
val interp = FontInterpolator()
- assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
}
@Test
@@ -118,7 +118,7 @@
.setFontVariationSettings("'wght' 1")
.build()
val resultFont = interp.lerp(startFont, endFont, 0.5f)
- for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) {
+ for (i in 0..interp.cacheMaxEntries + 1) {
val f1 = Font.Builder(sFont)
.setFontVariationSettings("'wght' ${i * 100}")
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
index adb10f0..5420c37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
@@ -1,4 +1,5 @@
set noparent
+# Bug component: 879035
include /services/core/java/com/android/server/biometrics/OWNERS
beverlyt@google.com
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index fa1067c..2248755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -114,6 +114,8 @@
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
+ @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
+ UdfpsKeyguardAccessibilityDelegate
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -144,7 +146,8 @@
configurationController, keyguardStateController, unlockedScreenOffAnimationController,
udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
- primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils
+ primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
+ udfpsKeyguardAccessibilityDelegate,
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index eef7ecc..72cd822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -217,6 +217,8 @@
private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
// Capture listeners so that they can be used to send events
@Captor
@@ -315,7 +317,8 @@
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
- mock(KeyguardFaceAuthInteractor.class));
+ mock(KeyguardFaceAuthInteractor.class),
+ mUdfpsKeyguardAccessibilityDelegate);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
new file mode 100644
index 0000000..921ff09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.testing.TestableLooper
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.argumentCaptor
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardAccessibilityDelegateTest : SysuiTestCase() {
+
+ @Mock private lateinit var keyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var hostView: View
+ private lateinit var underTest: UdfpsKeyguardAccessibilityDelegate
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ UdfpsKeyguardAccessibilityDelegate(
+ context.resources,
+ keyguardViewManager,
+ )
+ }
+
+ @Test
+ fun onInitializeAccessibilityNodeInfo_clickActionAdded() {
+ // WHEN node is initialized
+ val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+ underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+ // THEN a11y action is added
+ val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
+ verify(mockedNodeInfo).addAction(argumentCaptor.capture())
+
+ // AND the a11y action is a click action
+ assertEquals(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ argumentCaptor.value.id
+ )
+ }
+
+ @Test
+ fun performAccessibilityAction_actionClick_showsPrimaryBouncer() {
+ // WHEN click action is performed
+ val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+ underTest.performAccessibilityAction(
+ hostView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ null
+ )
+
+ // THEN primary bouncer shows
+ verify(keyguardViewManager).showPrimaryBouncer(anyBoolean())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 43767fc..032753a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -73,6 +73,7 @@
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
+ protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -167,7 +168,8 @@
mActivityLaunchAnimator,
mFeatureFlags,
mPrimaryBouncerInteractor,
- mAlternateBouncerInteractor);
+ mAlternateBouncerInteractor,
+ mUdfpsKeyguardAccessibilityDelegate);
return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 1f2b64d..263ce1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -56,6 +56,7 @@
foldProvider,
KeyguardTransitionInteractor(
keyguardTransitionRepository,
+ testScope.backgroundScope
),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index 689bb00..fff1b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -33,6 +33,7 @@
with(PromptAuthState(isAuthenticated = false)) {
assertThat(isNotAuthenticated).isTrue()
assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -43,6 +44,7 @@
with(PromptAuthState(isAuthenticated = true)) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -50,10 +52,12 @@
with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
- assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ assertThat(asExplicitlyConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ assertThat(asExplicitlyConfirmed().isAuthenticatedAndExplicitlyConfirmed).isTrue()
}
}
@@ -64,6 +68,7 @@
) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isTrue()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -79,6 +84,7 @@
) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 820f863..f892453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -407,6 +407,7 @@
whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
.thenReturn(true)
+ whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true)
// WHEN bouncer show is requested
underTest.show(true)
@@ -424,6 +425,25 @@
}
@Test
+ fun noDelayBouncer_biometricsAllowed_postureDoesNotAllowFaceAuth() {
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+ // GIVEN bouncer should not be delayed because device isn't in the right posture for
+ // face auth
+ whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(true)
+ whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false)
+
+ // WHEN bouncer show is requested
+ underTest.show(true)
+
+ // THEN primary show & primary showing soon are updated immediately
+ verify(repository).setPrimaryShow(true)
+ verify(repository).setPrimaryShowingSoon(false)
+ }
+
+ @Test
fun delayBouncerWhenActiveUnlockPossible() {
testScope.run {
mainHandler.setMode(FakeHandler.Mode.QUEUEING)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 039682c..a00e545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -11,7 +11,6 @@
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -23,9 +22,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.anyLong
import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -92,16 +89,10 @@
}
@Test
- fun testWakeUpCallsExecutor() {
- val mockExecutor: DelayableExecutor = mock()
- val mockCallback: Runnable = mock()
+ fun testWakeUpSetsExitAnimationsRunning() {
+ controller.wakeUp()
- controller.wakeUp(
- doneCallback = mockCallback,
- executor = mockExecutor,
- )
-
- verify(mockExecutor).executeDelayed(eq(mockCallback), anyLong())
+ verify(stateController).setExitAnimationsRunning(true)
}
@Test
@@ -112,10 +103,7 @@
verify(mockStartAnimator, never()).cancel()
- controller.wakeUp(
- doneCallback = mock(),
- executor = mock(),
- )
+ controller.wakeUp()
// Verify that we cancelled the start animator in favor of the exit
// animator.
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 c97eedb..d99f0da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -500,19 +499,15 @@
true /*shouldShowComplication*/);
mMainExecutor.runAllReady();
- final Runnable callback = mock(Runnable.class);
- mService.onWakeUp(callback);
- mMainExecutor.runAllReady();
- verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+ mService.onWakeUp();
+ verify(mDreamOverlayContainerViewController).wakeUp();
verify(mDreamOverlayCallbackController).onWakeUp();
}
@Test
public void testWakeUpBeforeStartDoesNothing() {
- final Runnable callback = mock(Runnable.class);
- mService.onWakeUp(callback);
- mMainExecutor.runAllReady();
- verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
+ mService.onWakeUp();
+ verify(mDreamOverlayContainerViewController, never()).wakeUp();
}
@Test
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
index b82ab91..3f9b198 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -28,6 +28,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
@@ -39,10 +40,12 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dreams.touch.scrim.ScrimController;
import com.android.systemui.dreams.touch.scrim.ScrimManager;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -57,6 +60,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Collections;
import java.util.Optional;
@SmallTest
@@ -100,21 +104,34 @@
@Mock
UiEventLogger mUiEventLogger;
+ @Mock
+ LockPatternUtils mLockPatternUtils;
+
+ FakeUserTracker mUserTracker;
+
private static final float TOUCH_REGION = .3f;
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+ private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+ 10,
+ /* name= */ "user10",
+ /* flags= */ 0
+ );
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
mScrimManager,
Optional.of(mCentralSurfaces),
mNotificationShadeWindowController,
mValueAnimatorCreator,
mVelocityTrackerFactory,
+ mLockPatternUtils,
+ mUserTracker,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
@@ -126,6 +143,9 @@
when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+ when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+ mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
}
/**
@@ -265,6 +285,32 @@
verifyScroll(.7f, Direction.DOWN, true, gestureListener);
}
+ /**
+ * Makes sure the expansion amount is proportional to (1 - scroll).
+ */
+ @Test
+ public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
+ when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
+
+ final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
+ 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);
+
+ reset(mScrimController);
+ assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
+ .isTrue();
+ // We should not expand since the keyguard is not secure
+ verify(mScrimController, never()).expand(any());
+ }
+
private void verifyScroll(float percent, Direction direction,
boolean isBouncerInitiallyShowing, GestureDetector.OnGestureListener gestureListener) {
final float distanceY = SCREEN_HEIGHT_PX * percent;
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 8bb6152..b0c1a34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
@@ -124,6 +125,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.Flow;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -184,6 +188,9 @@
/** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */
private boolean mKeyguardGoingAway = false;
+ private @Mock CoroutineDispatcher mDispatcher;
+ private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+
private FakeFeatureFlags mFeatureFlags;
private int mInitialUserId;
@@ -200,6 +207,8 @@
final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
when(testViewRoot.getView()).thenReturn(mock(View.class));
when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
+ when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
+ .thenReturn(mock(Flow.class));
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mViewMediator, mKeyguardBypassController,
@@ -250,6 +259,7 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
// GIVEN keyguard is not enabled and isn't showing
mViewMediator.onSystemReady();
@@ -810,7 +820,9 @@
mFeatureFlags,
mSecureSettings,
mSystemSettings,
- mSystemClock);
+ mSystemClock,
+ mDispatcher,
+ () -> mDreamingToLockscreenTransitionViewModel);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index eb97022..b4bd473 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -67,7 +67,10 @@
resourceTrimmer =
ResourceTrimmer(
keyguardInteractor,
- KeyguardTransitionInteractor(keyguardTransitionRepository),
+ KeyguardTransitionInteractor(
+ keyguardTransitionRepository,
+ testScope.backgroundScope
+ ),
globalWindowManager,
testScope.backgroundScope,
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 90b3a8f..92ec9a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -216,7 +216,7 @@
)
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(keyguardTransitionRepository)
+ KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 41ccfe2..80700e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -85,7 +85,8 @@
bouncerRepository = FakeKeyguardBouncerRepository()
faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
underTest =
SystemUIKeyguardFaceAuthInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 5de24e4..f63be61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -292,7 +292,8 @@
scope = testScope.backgroundScope,
transitionInteractor =
KeyguardTransitionInteractor(
- repository = keyguardTransitionRepository,
+ keyguardTransitionRepository,
+ testScope.backgroundScope
),
repository = keyguardRepository,
logger = logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index d66e420..fa4941c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -27,11 +27,14 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -46,11 +49,12 @@
private lateinit var underTest: KeyguardTransitionInteractor
private lateinit var repository: FakeKeyguardTransitionRepository
+ private val testScope = TestScope()
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- underTest = KeyguardTransitionInteractor(repository)
+ underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope)
}
@Test
@@ -108,17 +112,17 @@
}
@Test
- fun keyguardStateTests() = runTest {
+ fun finishedKeyguardStateTests() = testScope.runTest {
val finishedSteps by collectValues(underTest.finishedKeyguardState)
-
+ runCurrent()
val steps = mutableListOf<TransitionStep>()
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
steps.forEach {
@@ -126,7 +130,29 @@
runCurrent()
}
- assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
+ assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
+ }
+
+ @Test
+ fun startedKeyguardStateTests() = testScope.runTest {
+ val finishedSteps by collectValues(underTest.startedKeyguardState)
+ runCurrent()
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(finishedSteps).isEqualTo(listOf(OFF, PRIMARY_BOUNCER, AOD, GONE))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3042560..50075b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -108,7 +108,8 @@
keyguardInteractor = createKeyguardInteractor(),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromLockscreenTransitionInteractor.start()
@@ -117,7 +118,8 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromDreamingTransitionInteractor.start()
@@ -126,7 +128,8 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromAodTransitionInteractor.start()
@@ -135,7 +138,8 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromGoneTransitionInteractor.start()
@@ -144,7 +148,8 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromDozingTransitionInteractor.start()
@@ -153,7 +158,8 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromOccludedTransitionInteractor.start()
@@ -162,7 +168,8 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
)
fromAlternateBouncerTransitionInteractor.start()
@@ -171,48 +178,14 @@
scope = testScope,
keyguardInteractor = createKeyguardInteractor(),
keyguardTransitionRepository = mockTransitionRepository,
- keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
keyguardSecurityModel = keyguardSecurityModel,
)
fromPrimaryBouncerTransitionInteractor.start()
}
@Test
- fun dreamingToLockscreen() =
- testScope.runTest {
- // GIVEN a device is dreaming
- keyguardRepository.setDreamingWithOverlay(true)
- keyguardRepository.setWakefulnessModel(startingToWake())
- runCurrent()
-
- // GIVEN a prior transition has run to DREAMING
- runTransition(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
-
- // WHEN doze is complete
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- // AND dreaming has stopped
- keyguardRepository.setDreamingWithOverlay(false)
- advanceUntilIdle()
- // AND then occluded has stopped
- keyguardRepository.setKeyguardOccluded(false)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
- }
- // THEN a transition to BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
-
- coroutineContext.cancelChildren()
- }
-
- @Test
fun lockscreenToPrimaryBouncerViaBouncerShowingCall() =
testScope.runTest {
// GIVEN a device that has at least woken up
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 4440946..08e99dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.LightRevealScrim
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -45,7 +46,7 @@
private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
private val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(fakeKeyguardTransitionRepository)
+ KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope)
private lateinit var underTest: LightRevealScrimInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index cdd06ac..a341346 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -25,11 +25,12 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.util.mockito.mock
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -42,13 +43,12 @@
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
- private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository)
- underTest = DreamingToLockscreenTransitionViewModel(interactor)
+ val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ underTest = DreamingToLockscreenTransitionViewModel(interactor, mock())
}
@Test
@@ -60,17 +60,15 @@
val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- // Should start running here...
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(0.6f))
- // ...up to here
repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(5)
+ assertThat(values.size).isEqualTo(7)
values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 40511a0..694539b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -29,6 +29,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -45,7 +46,7 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository)
+ val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
underTest = GoneToDreamingTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 9c9aadf..29886d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -212,6 +212,7 @@
transitionInteractor =
KeyguardTransitionInteractor(
repository = FakeKeyguardTransitionRepository(),
+ scope = testScope.backgroundScope
),
repository = repository,
logger = UiEventLoggerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index c98058d..ea17751 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -29,6 +29,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -45,7 +46,7 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository)
+ val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
underTest = LockscreenToDreamingTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 031b7fb..bf56a98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -29,6 +29,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -45,7 +46,7 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository)
+ val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
underTest = LockscreenToOccludedTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index c7ff882..34da26e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -29,6 +29,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -45,7 +46,7 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository)
+ val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
underTest = OccludedToLockscreenTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 4919a66..f88b71d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -33,6 +33,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -54,7 +55,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository)
+ val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
underTest =
PrimaryBouncerToGoneTransitionViewModel(
interactor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 07f7c15..2aff90c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -63,6 +63,7 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -146,7 +147,7 @@
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
- KeyguardTransitionInteractor(repository = transitionRepository),
+ KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 7b673bc..f6075ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -2420,7 +2420,6 @@
@Test
fun playTurbulenceNoise_finishesAfterDuration() {
- fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
val semanticActions =
@@ -2452,6 +2451,29 @@
}
@Test
+ fun playTurbulenceNoise_whenPlaybackStateIsNotPlaying_doesNotPlayTurbulence() {
+ fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
+
+ val semanticActions =
+ MediaButton(
+ custom0 =
+ MediaAction(
+ icon = null,
+ action = {},
+ contentDescription = "custom0",
+ background = null
+ ),
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action0.callOnClick()
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
fun outputSwitcher_hasCustomIntent_openOverLockscreen() {
// When the device for a media player has an intent that opens over lockscreen
val pendingIntent = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index 19f9960..9e54224 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -99,6 +99,7 @@
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = keyguardTransitionRepository,
+ scope = testScope.backgroundScope
),
falsingManager = falsingManager,
shadeController = shadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 87ca9df..fcda5f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -50,10 +50,8 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -169,9 +167,7 @@
}
@Test
- public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true);
+ public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
QSFragment fragment = resumeAndGetFragment();
setIsLargeScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
@@ -188,25 +184,6 @@
}
@Test
- public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(false);
- QSFragment fragment = resumeAndGetFragment();
- setIsLargeScreen();
- setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
- boolean isTransitioningToFullShade = true;
- float transitionProgress = 0.5f;
- float squishinessFraction = 0.5f;
- when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
-
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
- squishinessFraction);
-
- assertThat(mQsFragmentView.getAlpha())
- .isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
- }
-
- @Test
public void
transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
QSFragment fragment = resumeAndGetFragment();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index e969112..af40e5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -111,8 +111,7 @@
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
- @Mock
- private lateinit var unfoldTransitionProgressProvider:
+ @Mock private lateinit var unfoldTransitionProgressProvider:
Optional<UnfoldTransitionProgressProvider>
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock
@@ -195,6 +194,7 @@
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = FakeKeyguardTransitionRepository(),
+ scope = testScope.backgroundScope
),
falsingManager = FalsingManagerFake(),
shadeController = shadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9fcffee..d3ecc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -207,6 +207,7 @@
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = FakeKeyguardTransitionRepository(),
+ scope = testScope.backgroundScope
),
falsingManager = FalsingManagerFake(),
shadeController = shadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index cbf5485..a5bd2ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -2,26 +2,16 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.shade.STATE_CLOSED
-import com.android.systemui.shade.STATE_OPEN
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -30,10 +20,6 @@
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock private lateinit var headsUpManager: HeadsUpManager
- @Mock private lateinit var featureFlags: FeatureFlags
- private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -41,131 +27,25 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
context.ensureTestableResources()
- controller =
- ScrimShadeTransitionController(
- configurationController,
- dumpManager,
- scrimController,
- context.resources,
- statusBarStateController,
- headsUpManager,
- featureFlags)
+ controller = ScrimShadeTransitionController(dumpManager, scrimController)
controller.onPanelStateChanged(STATE_OPENING)
}
@Test
- fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() {
- setSplitShadeEnabled(false)
-
+ fun onPanelExpansionChanged_setsFractionEqualToEventFraction() {
controller.onPanelExpansionChanged(EXPANSION_EVENT)
verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
}
- @Test
- fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
- whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
- val scrimShadeTransitionDistance =
- context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- val expectedFraction = EXPANSION_EVENT.dragDownPxAmount / scrimShadeTransitionDistance
- verify(scrimController).setRawPanelExpansionFraction(expectedFraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
- whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
- val scrimShadeTransitionDistance =
- context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(
- EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
-
- verify(scrimController).setRawPanelExpansionFraction(1f)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
- whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
-
- verify(scrimController).setRawPanelExpansionFraction(0f)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_onLockedShade_setsFractionEqualToEventFraction() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE_LOCKED)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
- whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true)
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.KEYGUARD)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
- controller.onPanelStateChanged(STATE_OPEN)
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.KEYGUARD)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
- controller.onPanelStateChanged(STATE_CLOSED)
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.KEYGUARD)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- configurationController.notifyConfigurationChanged()
- }
-
companion object {
val EXPANSION_EVENT =
ShadeExpansionChangeEvent(
- fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
+ fraction = 0.5f,
+ expanded = true,
+ tracking = true,
+ dragDownPxAmount = 10f
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index b724175..0b1753f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -555,60 +555,6 @@
}
/**
- * Ensures a subscription is predicated on its precondition.
- */
- @Test
- public void testPrecondition() {
- mCondition1.fakeUpdateCondition(false);
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
-
- mCondition2.fakeUpdateCondition(false);
-
- // Create a nested condition
- mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(callback)
- .addPrecondition(mCondition1)
- .addCondition(mCondition2)
- .build());
-
- mExecutor.runAllReady();
-
- // Ensure the nested condition callback is not called at all.
- verify(callback, never()).onActiveChanged(anyBoolean());
- verify(callback, never()).onConditionsChanged(anyBoolean());
-
- // Update the condition to true and ensure that the nested condition is not triggered.
- mCondition2.fakeUpdateCondition(true);
- verify(callback, never()).onConditionsChanged(anyBoolean());
- mCondition2.fakeUpdateCondition(false);
-
- // Set precondition and make sure the inner condition becomes active and reports that
- // conditions aren't met
- mCondition1.fakeUpdateCondition(true);
- mExecutor.runAllReady();
-
- verify(callback).onActiveChanged(eq(true));
- verify(callback).onConditionsChanged(eq(false));
-
- Mockito.clearInvocations(callback);
-
- // Update the condition and make sure the callback is updated.
- mCondition2.fakeUpdateCondition(true);
- mExecutor.runAllReady();
-
- verify(callback).onConditionsChanged(true);
-
- Mockito.clearInvocations(callback);
- // Invalidate precondition and make sure callback is informed, but the last state is
- // not affected.
- mCondition1.fakeUpdateCondition(false);
- mExecutor.runAllReady();
-
- verify(callback).onActiveChanged(eq(false));
- verify(callback, never()).onConditionsChanged(anyBoolean());
- }
-
- /**
* Ensure preconditions are applied to every subscription added to a monitor.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 283efe2..257cc5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -221,16 +221,35 @@
}
@Test
- fun hunExtensionCancelledWhenHunActionPressed() {
+ fun actionPressCancelsExistingLifetimeExtension() {
whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
addHUN(entry)
+
whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
- assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason = */ 0))
+
actionPressListener.accept(entry)
- executor.advanceClockToLast()
executor.runAllReady()
- verify(headsUpManager, times(1)).removeNotification(eq(entry.key), eq(true))
+ verify(endLifetimeExtension, times(1)).onEndLifetimeExtension(notifLifetimeExtender, entry)
+
+ collectionListener.onEntryRemoved(entry, /* reason = */ 0)
+ verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
+ }
+
+ @Test
+ fun actionPressPreventsFutureLifetimeExtension() {
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+
+ actionPressListener.accept(entry)
+ verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry)
+
+ whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true)
+ assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+
+ collectionListener.onEntryRemoved(entry, /* reason = */ 0)
+ verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
}
@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
index 05b70eb..8c27a7dd 100644
--- 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
@@ -8,7 +8,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
@@ -311,9 +310,8 @@
}
@Test
- fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+ fun updateState_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
- whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
whenever(ambientState.isSmallScreen).thenReturn(false)
whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
.thenReturn(0.123f)
@@ -325,20 +323,6 @@
}
@Test
- fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
- val expansionFraction = 0.6f
- whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
- whenever(ambientState.isSmallScreen).thenReturn(false)
- whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
- .thenReturn(0.123f)
-
- updateState_expansionChanging_shelfAlphaUpdated(
- expansionFraction = expansionFraction,
- expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
- )
- }
-
- @Test
fun updateState_expansionChangingWhileBouncerInTransit_shelfAlphaUpdated() {
whenever(ambientState.isBouncerInTransit).thenReturn(true)
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 e12d179..7b1565e 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
@@ -9,7 +9,6 @@
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
@@ -160,12 +159,10 @@
}
@Test
- fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+ fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
val surfaceAlpha = 123f
ambientState.isSmallScreen = false
- whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true)
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
.thenReturn(surfaceAlpha)
@@ -177,23 +174,6 @@
}
@Test
- fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
- val expansionFraction = 0.6f
- val surfaceAlpha = 123f
- ambientState.isSmallScreen = false
- whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(false)
- whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
- whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
- .thenReturn(surfaceAlpha)
-
- resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = expansionFraction,
- expectedAlpha = getContentAlpha(expansionFraction),
- )
- }
-
- @Test
fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 044dd58..ab80158 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -61,7 +61,6 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -697,9 +696,7 @@
}
@Test
- public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true);
+ public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -737,48 +734,6 @@
mScrimBehind, OPAQUE));
}
-
- @Test
- public void transitionToUnlocked_nonClippedQs_flagFalse() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(false);
- mScrimController.setClipsQsScrim(false);
- mScrimController.setRawPanelExpansionFraction(0f);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, TRANSPARENT,
- mScrimBehind, TRANSPARENT));
-
- assertScrimTinted(Map.of(
- mNotificationsScrim, false,
- mScrimInFront, false,
- mScrimBehind, true
- ));
-
- // Back scrim should be visible after start dragging
- mScrimController.setRawPanelExpansionFraction(0.29f);
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, TRANSPARENT,
- mScrimBehind, SEMI_TRANSPARENT));
-
- // Back scrim should be opaque at 30%
- mScrimController.setRawPanelExpansionFraction(0.3f);
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, TRANSPARENT,
- mScrimBehind, OPAQUE));
-
- // Then, notification scrim should fade in
- mScrimController.setRawPanelExpansionFraction(0.31f);
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, SEMI_TRANSPARENT,
- mScrimBehind, OPAQUE));
- }
-
@Test
public void scrimStateCallback() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index c8c24a7..6301fa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -47,7 +47,8 @@
testScope = TestScope(UnconfinedTestDispatcher())
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+ val interactor =
+ KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 487d26d..a797e03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -20,7 +20,6 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -346,4 +345,17 @@
assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
mUiEventLoggerFake.eventId(0));
}
+
+ @Test
+ public void testSetUserActionMayIndirectlyRemove() {
+ NotificationEntry notifEntry = new NotificationEntryBuilder()
+ .setSbn(createNewNotification(/* id= */ 0))
+ .build();
+
+ mHeadsUpManager.showNotification(notifEntry);
+ assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+
+ mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
+ assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2ea6368..ef3a332 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -28,6 +28,7 @@
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -584,6 +585,8 @@
}
String ringerContainerDescription = mDialog.getSelectedRingerContainerDescription();
+ assumeNotNull(ringerContainerDescription);
+
String ringerDescription = mContext.getString(
mDialog.getStringDescriptionResourceForRingerMode(ringerMode));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
index 9179efc..e470406 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -57,8 +57,7 @@
@Before
public void sysuiSetup() throws ExecutionException, InterruptedException {
- SystemUIInitializer initializer =
- SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 8bbd58d..de177168 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -90,8 +90,7 @@
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
- SystemUIInitializer initializer =
- SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 1a57bc1..ec4203e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -63,6 +63,7 @@
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArrayMap;
import android.util.LocalLog;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -334,7 +335,8 @@
// of time.
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.onSwitchInputMethod();
}
@@ -366,11 +368,12 @@
boolean isTemporary) {
mAugmentedAutofillState.setServiceInfo(userId, serviceName, isTemporary);
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service == null) {
// If we cannot get the service from the services cache, it will call
// updateRemoteAugmentedAutofillService() finally. Skip call this update again.
- getServiceForUserLocked(userId);
+ getServiceForUserWithLocalBinderIdentityLocked(userId);
} else {
service.updateRemoteAugmentedAutofillService();
}
@@ -380,17 +383,46 @@
private void onFieldClassificationServiceNameChanged(
@UserIdInt int userId, @Nullable String serviceName, boolean isTemporary) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service == null) {
// If we cannot get the service from the services cache, it will call
// updateRemoteFieldClassificationService() finally. Skip call this update again.
- getServiceForUserLocked(userId);
+ getServiceForUserWithLocalBinderIdentityLocked(userId);
} else {
service.updateRemoteFieldClassificationService();
}
}
}
+ @GuardedBy("mLock")
+ @Nullable
+ private AutofillManagerServiceImpl getServiceForUserWithLocalBinderIdentityLocked(int userId) {
+ final long token = Binder.clearCallingIdentity();
+ AutofillManagerServiceImpl managerService = null;
+ try {
+ managerService = getServiceForUserLocked(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return managerService;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private AutofillManagerServiceImpl peekServiceForUserWithLocalBinderIdentityLocked(int userId) {
+ final long token = Binder.clearCallingIdentity();
+ AutofillManagerServiceImpl managerService = null;
+ try {
+ managerService = peekServiceForUserLocked(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return managerService;
+ }
+
@Override // from AbstractMasterSystemService
protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
boolean disabled) {
@@ -1038,7 +1070,8 @@
mUi.hideAll(null);
synchronized (mLock) {
final AutofillManagerServiceImpl service =
- getServiceForUserLocked(UserHandle.getCallingUserId());
+ getServiceForUserWithLocalBinderIdentityLocked(
+ UserHandle.getCallingUserId());
service.onBackKeyPressed();
}
}
@@ -1537,20 +1570,27 @@
public void addClient(IAutoFillManagerClient client, ComponentName componentName,
int userId, IResultReceiver receiver) {
int flags = 0;
- synchronized (mLock) {
- final int enabledFlags = getServiceForUserLocked(userId).addClientLocked(client,
- componentName);
- if (enabledFlags != 0) {
- flags |= enabledFlags;
+ try {
+ synchronized (mLock) {
+ final int enabledFlags =
+ getServiceForUserWithLocalBinderIdentityLocked(userId)
+ .addClientLocked(client, componentName);
+ if (enabledFlags != 0) {
+ flags |= enabledFlags;
+ }
+ if (sDebug) {
+ flags |= AutofillManager.FLAG_ADD_CLIENT_DEBUG;
+ }
+ if (sVerbose) {
+ flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
+ }
}
- if (sDebug) {
- flags |= AutofillManager.FLAG_ADD_CLIENT_DEBUG;
- }
- if (sVerbose) {
- flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
- }
+ } catch (Exception ex) {
+ // Don't do anything, send back default flags
+ Log.wtf(TAG, "addClient(): failed " + ex.toString());
+ } finally {
+ send(receiver, flags);
}
- send(receiver, flags);
}
@Override
@@ -1569,7 +1609,8 @@
public void setAuthenticationResult(Bundle data, int sessionId, int authenticationId,
int userId) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ getServiceForUserWithLocalBinderIdentityLocked(userId);
service.setAuthenticationResultLocked(data, sessionId, authenticationId,
getCallingUid());
}
@@ -1578,7 +1619,8 @@
@Override
public void setHasCallback(int sessionId, int userId, boolean hasIt) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ getServiceForUserWithLocalBinderIdentityLocked(userId);
service.setHasCallback(sessionId, getCallingUid(), hasIt);
}
}
@@ -1608,7 +1650,8 @@
final int taskId = mAm.getTaskIdForActivity(activityToken, false);
final long result;
synchronized (mLock) {
- final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ getServiceForUserWithLocalBinderIdentityLocked(userId);
result = service.startSessionLocked(activityToken, taskId, getCallingUid(),
clientCallback, autofillId, bounds, value, hasCallback, clientActivity,
compatMode, mAllowInstantService, flags);
@@ -1624,51 +1667,72 @@
@Override
public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException {
+ FillEventHistory fillEventHistory = null;
final int userId = UserHandle.getCallingUserId();
- FillEventHistory fillEventHistory = null;
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- fillEventHistory = service.getFillEventHistory(getCallingUid());
- } else if (sVerbose) {
- Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ fillEventHistory = service.getFillEventHistory(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
+ }
}
+ } catch (Exception ex) {
+ // Do not raise the exception, just send back the null response
+ Log.wtf(TAG, "getFillEventHistory(): failed " + ex.toString());
+ } finally {
+ send(receiver, fillEventHistory);
}
- send(receiver, fillEventHistory);
}
@Override
public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException {
+ UserData userData = null;
final int userId = UserHandle.getCallingUserId();
- UserData userData = null;
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- userData = service.getUserData(getCallingUid());
- } else if (sVerbose) {
- Slog.v(TAG, "getUserData(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ userData = service.getUserData(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getUserData(): no service for " + userId);
+ }
}
+ } catch (Exception ex) {
+ // Do not raise the exception, just send back the null response
+ Log.wtf(TAG, "getUserData(): failed " + ex.toString());
+ } finally {
+ send(receiver, userData);
}
- send(receiver, userData);
}
@Override
public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException {
- final int userId = UserHandle.getCallingUserId();
UserData userData = null;
+ final int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- userData = service.getUserData(getCallingUid());
- } else if (sVerbose) {
- Slog.v(TAG, "getUserDataId(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ userData = service.getUserData(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "getUserDataId(): no service for " + userId);
+ }
}
+ } catch (Exception ex) {
+ // Do not raise the exception, just send back the null response
+ Log.wtf(TAG, "getUserDataId(): failed " + ex.toString());
+ } finally {
+ final String userDataId = userData == null ? null : userData.getId();
+ send(receiver, userDataId);
}
- final String userDataId = userData == null ? null : userData.getId();
- send(receiver, userDataId);
}
@Override
@@ -1676,7 +1740,8 @@
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.setUserData(getCallingUid(), userData);
} else if (sVerbose) {
@@ -1688,124 +1753,171 @@
@Override
public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver)
throws RemoteException {
- final int userId = UserHandle.getCallingUserId();
boolean enabled = false;
+ final int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- enabled = service.isFieldClassificationEnabled(getCallingUid());
- } else if (sVerbose) {
- Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ enabled = service.isFieldClassificationEnabled(getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
+ }
}
+ } catch (Exception ex) {
+ // Do not raise the exception, just send back false
+ Log.wtf(TAG, "isFieldClassificationEnabled(): failed " + ex.toString());
+ } finally {
+ send(receiver, enabled);
}
- send(receiver, enabled);
}
@Override
public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver)
throws RemoteException {
- final int userId = UserHandle.getCallingUserId();
String algorithm = null;
+ final int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
- } else {
- if (sVerbose) {
- Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
+ }
}
- }
+ }
+ } catch (Exception ex) {
+ // Do not raise the exception, just send back null
+ Log.wtf(TAG, "getDefaultFieldClassificationAlgorithm(): failed " + ex.toString());
+ } finally {
+ send(receiver, algorithm);
}
- send(receiver, algorithm);
+
}
@Override
public void setAugmentedAutofillWhitelist(@Nullable List<String> packages,
@Nullable List<ComponentName> activities, @NonNull IResultReceiver receiver)
throws RemoteException {
+ boolean ok = false;
final int userId = UserHandle.getCallingUserId();
- boolean ok;
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- ok = service.setAugmentedAutofillWhitelistLocked(packages, activities,
- getCallingUid());
- } else {
- if (sVerbose) {
- Slog.v(TAG, "setAugmentedAutofillWhitelist(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ ok = service.setAugmentedAutofillWhitelistLocked(packages, activities,
+ getCallingUid());
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "setAugmentedAutofillWhitelist(): no service for "
+ + userId);
+ }
}
- ok = false;
}
+ } catch (Exception ex) {
+ // Do not raise the exception, return the default value
+ Log.wtf(TAG, "setAugmentedAutofillWhitelist(): failed " + ex.toString());
+ } finally {
+ send(receiver,
+ ok ? AutofillManager.RESULT_OK
+ : AutofillManager.RESULT_CODE_NOT_SERVICE);
}
- send(receiver,
- ok ? AutofillManager.RESULT_OK : AutofillManager.RESULT_CODE_NOT_SERVICE);
}
@Override
public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver)
throws RemoteException {
- final int userId = UserHandle.getCallingUserId();
String[] algorithms = null;
+ final int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid());
- } else {
- if (sVerbose) {
- Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ algorithms = service
+ .getAvailableFieldClassificationAlgorithms(getCallingUid());
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
+ }
}
}
+ } catch (Exception ex) {
+ // Do not raise the exception, return null
+ Log.wtf(TAG, "getAvailableFieldClassificationAlgorithms(): failed "
+ + ex.toString());
+ } finally {
+ send(receiver, algorithms);
}
- send(receiver, algorithms);
}
@Override
public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver)
throws RemoteException {
+ ComponentName componentName = null;
final int userId = UserHandle.getCallingUserId();
- ComponentName componentName = null;
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- componentName = service.getServiceComponentName();
- } else if (sVerbose) {
- Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ componentName = service.getServiceComponentName();
+ } else if (sVerbose) {
+ Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
+ }
}
+ } catch (Exception ex) {
+ Log.wtf(TAG, "getAutofillServiceComponentName(): failed " + ex.toString());
+ } finally {
+ send(receiver, componentName);
}
- send(receiver, componentName);
}
@Override
public void restoreSession(int sessionId, @NonNull IBinder activityToken,
@NonNull IBinder appCallback, @NonNull IResultReceiver receiver)
throws RemoteException {
- final int userId = UserHandle.getCallingUserId();
- Objects.requireNonNull(activityToken, "activityToken");
- Objects.requireNonNull(appCallback, "appCallback");
-
boolean restored = false;
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
- appCallback);
- } else if (sVerbose) {
- Slog.v(TAG, "restoreSession(): no service for " + userId);
+ final int userId = UserHandle.getCallingUserId();
+
+ try {
+ Objects.requireNonNull(activityToken, "activityToken");
+ Objects.requireNonNull(appCallback, "appCallback");
+
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
+ appCallback);
+ } else if (sVerbose) {
+ Slog.v(TAG, "restoreSession(): no service for " + userId);
+ }
}
+ } catch (Exception ex) {
+ // Do not propagate exception, send back status
+ Log.wtf(TAG, "restoreSession(): failed " + ex.toString());
+ } finally {
+ send(receiver, restored);
}
- send(receiver, restored);
}
@Override
public void updateSession(int sessionId, AutofillId autoFillId, Rect bounds,
AutofillValue value, int action, int flags, int userId) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.updateSessionLocked(sessionId, getCallingUid(), autoFillId, bounds,
value, action, flags);
@@ -1818,7 +1930,8 @@
@Override
public void setAutofillFailure(int sessionId, @NonNull List<AutofillId> ids, int userId) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.setAutofillFailureLocked(sessionId, getCallingUid(), ids);
} else if (sVerbose) {
@@ -1831,7 +1944,8 @@
public void finishSession(int sessionId, int userId,
@AutofillCommitReason int commitReason) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.finishSessionLocked(sessionId, getCallingUid(), commitReason);
} else if (sVerbose) {
@@ -1843,19 +1957,22 @@
@Override
public void cancelSession(int sessionId, int userId) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.cancelSessionLocked(sessionId, getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "cancelSession(): no service for " + userId);
}
}
+
}
@Override
public void disableOwnedAutofillServices(int userId) {
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
service.disableOwnedAutofillServicesLocked(Binder.getCallingUid());
} else if (sVerbose) {
@@ -1867,21 +1984,36 @@
@Override
public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) {
boolean supported = false;
- synchronized (mLock) {
- supported = !isDisabledLocked(userId);
+
+ try {
+ synchronized (mLock) {
+ supported = !isDisabledLocked(userId);
+ }
+ } catch (Exception ex) {
+ // Do not propagate exception
+ Log.wtf(TAG, "isServiceSupported(): failed " + ex.toString());
+ } finally {
+ send(receiver, supported);
}
- send(receiver, supported);
}
@Override
public void isServiceEnabled(int userId, @NonNull String packageName,
@NonNull IResultReceiver receiver) {
boolean enabled = false;
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
- enabled = Objects.equals(packageName, service.getServicePackageName());
+
+ try {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ enabled = Objects.equals(packageName, service.getServicePackageName());
+ }
+ } catch (Exception ex) {
+ // Do not propagate exception
+ Log.wtf(TAG, "isServiceEnabled(): failed " + ex.toString());
+ } finally {
+ send(receiver, enabled);
}
- send(receiver, enabled);
}
@Override
@@ -1891,8 +2023,9 @@
|| operation == AutofillManager.PENDING_UI_OPERATION_RESTORE,
"invalid operation: %d", operation);
synchronized (mLock) {
- final AutofillManagerServiceImpl service = peekServiceForUserLocked(
- UserHandle.getCallingUserId());
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(
+ UserHandle.getCallingUserId());
if (service != null) {
service.onPendingSaveUi(operation, token);
}
@@ -1907,7 +2040,7 @@
boolean uiOnly = false;
if (args != null) {
for (String arg : args) {
- switch(arg) {
+ switch (arg) {
case "--no-history":
showHistory = false;
break;
@@ -1934,27 +2067,38 @@
try {
sDebug = sVerbose = true;
synchronized (mLock) {
- pw.print("sDebug: "); pw.print(realDebug);
- pw.print(" sVerbose: "); pw.println(realVerbose);
+ pw.print("sDebug: ");
+ pw.print(realDebug);
+ pw.print(" sVerbose: ");
+ pw.println(realVerbose);
pw.print("Flags: ");
synchronized (mFlagLock) {
- pw.print("mPccClassificationEnabled="); pw.print(mPccClassificationEnabled);
+ pw.print("mPccClassificationEnabled=");
+ pw.print(mPccClassificationEnabled);
pw.print(";");
- pw.print("mPccPreferProviderOverPcc="); pw.print(mPccPreferProviderOverPcc);
+ pw.print("mPccPreferProviderOverPcc=");
+ pw.print(mPccPreferProviderOverPcc);
pw.print(";");
- pw.print("mPccUseFallbackDetection="); pw.print(mPccUseFallbackDetection);
+ pw.print("mPccUseFallbackDetection=");
+ pw.print(mPccUseFallbackDetection);
pw.print(";");
- pw.print("mPccProviderHints="); pw.println(mPccProviderHints);
+ pw.print("mPccProviderHints=");
+ pw.println(mPccProviderHints);
}
// Dump per-user services
dumpLocked("", pw);
- mAugmentedAutofillResolver.dumpShort(pw); pw.println();
- pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount);
- pw.print("Max visible datasets: "); pw.println(sVisibleDatasetsMaxCount);
+ mAugmentedAutofillResolver.dumpShort(pw);
+ pw.println();
+ pw.print("Max partitions per session: ");
+ pw.println(sPartitionMaxCount);
+ pw.print("Max visible datasets: ");
+ pw.println(sVisibleDatasetsMaxCount);
if (sFullScreenMode != null) {
- pw.print("Overridden full-screen mode: "); pw.println(sFullScreenMode);
+ pw.print("Overridden full-screen mode: ");
+ pw.println(sFullScreenMode);
}
- pw.println("User data constraints: "); UserData.dumpConstraints(prefix, pw);
+ pw.println("User data constraints: ");
+ UserData.dumpConstraints(prefix, pw);
mUi.dump(pw);
pw.print("Autofill Compat State: ");
mAutofillCompatState.dump(prefix, pw);
@@ -1969,11 +2113,17 @@
pw.print("Augmented Service Request Timeout: ");
pw.println(mAugmentedServiceRequestTimeoutMs);
if (showHistory) {
- pw.println(); pw.println("Requests history:"); pw.println();
+ pw.println();
+ pw.println("Requests history:");
+ pw.println();
mRequestsHistory.reverseDump(fd, pw, args);
- pw.println(); pw.println("UI latency history:"); pw.println();
+ pw.println();
+ pw.println("UI latency history:");
+ pw.println();
mUiLatencyHistory.reverseDump(fd, pw, args);
- pw.println(); pw.println("WTF history:"); pw.println();
+ pw.println();
+ pw.println("WTF history:");
+ pw.println();
mWtfHistory.reverseDump(fd, pw, args);
}
pw.println("Augmented Autofill State: ");
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a5c4ac7..639f279 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -33,6 +33,7 @@
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_PCC_DETECTION;
import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
+import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
@@ -611,6 +612,9 @@
/** Whether the fill dialog UI is disabled. */
private boolean mFillDialogDisabled;
+
+ /** Whether current screen has credman field. */
+ private boolean mScreenHasCredmanField;
}
/**
@@ -3248,6 +3252,18 @@
final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
/*
+ * Don't show save if the session has credman field
+ */
+ if (mSessionFlags.mScreenHasCredmanField) {
+ if (sVerbose) {
+ Slog.v(TAG, "Call to Session#showSaveLocked() rejected - "
+ + "there is credman field in screen");
+ }
+ return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
+ Event.NO_SAVE_UI_REASON_NONE);
+ }
+
+ /*
* The Save dialog is only shown if all conditions below are met:
*
* - saveInfo is not null.
@@ -3988,6 +4004,10 @@
return;
}
+ if ((flags & FLAG_SCREEN_HAS_CREDMAN_FIELD) != 0) {
+ mSessionFlags.mScreenHasCredmanField = true;
+ }
+
switch(action) {
case ACTION_START_SESSION:
// View is triggering autofill.
@@ -4466,7 +4486,7 @@
private boolean isFillDialogUiEnabled() {
synchronized (mLock) {
- return !mSessionFlags.mFillDialogDisabled;
+ return !mSessionFlags.mFillDialogDisabled && !mSessionFlags.mScreenHasCredmanField;
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index fb99bff..a831401 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -18,17 +18,20 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.hardware.SensorDirectChannel;
+import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -36,6 +39,9 @@
import com.android.server.sensors.SensorManagerInternal;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -54,46 +60,64 @@
private final Object mLock = new Object();
private final int mVirtualDeviceId;
+
@GuardedBy("mLock")
private final ArrayMap<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+ // This device's sensors, keyed by sensor handle.
+ @GuardedBy("mLock")
+ private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
+ @GuardedBy("mLock")
+ private List<VirtualSensor> mVirtualSensorList = null;
+
@NonNull
private final SensorManagerInternal.RuntimeSensorCallback mRuntimeSensorCallback;
private final SensorManagerInternal mSensorManagerInternal;
private final VirtualDeviceManagerInternal mVdmInternal;
- public SensorController(int virtualDeviceId,
- @Nullable IVirtualSensorCallback virtualSensorCallback) {
+ public SensorController(@NonNull IVirtualDevice virtualDevice, int virtualDeviceId,
+ @Nullable IVirtualSensorCallback virtualSensorCallback,
+ @NonNull List<VirtualSensorConfig> sensors) {
mVirtualDeviceId = virtualDeviceId;
mRuntimeSensorCallback = new RuntimeSensorCallbackWrapper(virtualSensorCallback);
mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
+ createSensors(virtualDevice, sensors);
}
void close() {
synchronized (mLock) {
mSensorDescriptors.values().forEach(
- descriptor -> mSensorManagerInternal.removeRuntimeSensor(
- descriptor.getHandle()));
+ descriptor -> mSensorManagerInternal.removeRuntimeSensor(descriptor.mHandle));
mSensorDescriptors.clear();
+ mVirtualSensors.clear();
+ mVirtualSensorList = null;
}
}
- int createSensor(@NonNull IBinder sensorToken, @NonNull VirtualSensorConfig config) {
- Objects.requireNonNull(sensorToken);
- Objects.requireNonNull(config);
+ private void createSensors(@NonNull IVirtualDevice virtualDevice,
+ @NonNull List<VirtualSensorConfig> configs) {
+ Objects.requireNonNull(virtualDevice);
+ final long token = Binder.clearCallingIdentity();
try {
- return createSensorInternal(sensorToken, config);
+ for (VirtualSensorConfig config : configs) {
+ createSensorInternal(virtualDevice, config);
+ }
} catch (SensorCreationException e) {
- throw new RuntimeException(
- "Failed to create virtual sensor '" + config.getName() + "'.", e);
+ throw new RuntimeException("Failed to create virtual sensor", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
- private int createSensorInternal(IBinder sensorToken, VirtualSensorConfig config)
+ private void createSensorInternal(@NonNull IVirtualDevice virtualDevice,
+ @NonNull VirtualSensorConfig config)
throws SensorCreationException {
+ Objects.requireNonNull(config);
if (config.getType() <= 0) {
- throw new SensorCreationException("Received an invalid virtual sensor type.");
+ throw new SensorCreationException(
+ "Received an invalid virtual sensor type (config name '" + config.getName()
+ + "').");
}
final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
config.getType(), config.getName(),
@@ -101,15 +125,20 @@
config.getResolution(), config.getPower(), config.getMinDelay(),
config.getMaxDelay(), config.getFlags(), mRuntimeSensorCallback);
if (handle <= 0) {
- throw new SensorCreationException("Received an invalid virtual sensor handle.");
+ throw new SensorCreationException(
+ "Received an invalid virtual sensor handle '" + config.getName() + "'.");
}
+ SensorDescriptor sensorDescriptor = new SensorDescriptor(
+ handle, config.getType(), config.getName());
+ final IBinder sensorToken =
+ new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
+ VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
+ virtualDevice, sensorToken);
synchronized (mLock) {
- SensorDescriptor sensorDescriptor = new SensorDescriptor(
- handle, config.getType(), config.getName());
mSensorDescriptors.put(sensorToken, sensorDescriptor);
+ mVirtualSensors.put(handle, sensor);
}
- return handle;
}
boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
@@ -126,17 +155,25 @@
}
}
- void unregisterSensor(@NonNull IBinder token) {
- Objects.requireNonNull(token);
+ @Nullable
+ VirtualSensor getSensorByHandle(int handle) {
synchronized (mLock) {
- final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
- if (sensorDescriptor == null) {
- throw new IllegalArgumentException("Could not unregister sensor for given token");
- }
- mSensorManagerInternal.removeRuntimeSensor(sensorDescriptor.getHandle());
+ return mVirtualSensors.get(handle);
}
}
+ List<VirtualSensor> getSensorList() {
+ synchronized (mLock) {
+ if (mVirtualSensorList == null) {
+ mVirtualSensorList = new ArrayList<>(mVirtualSensors.size());
+ for (int i = 0; i < mVirtualSensors.size(); ++i) {
+ mVirtualSensorList.add(mVirtualSensors.valueAt(i));
+ }
+ mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
+ }
+ return mVirtualSensorList;
+ }
+ }
void dump(@NonNull PrintWriter fout) {
fout.println(" SensorController: ");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index e1b37e5..6b948bd 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -42,7 +42,6 @@
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
-import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -94,7 +93,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,11 +155,6 @@
@GuardedBy("mVirtualDeviceLock")
@Nullable
private LocaleList mLocaleList = null;
- // This device's sensors, keyed by sensor handle.
- @GuardedBy("mVirtualDeviceLock")
- private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
- @GuardedBy("mVirtualDeviceLock")
- private List<VirtualSensor> mVirtualSensorList = null;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@@ -218,7 +211,6 @@
ownerUid,
deviceId,
/* inputController= */ null,
- /* sensorController= */ null,
cameraAccessController,
pendingTrampolineCallback,
activityListener,
@@ -237,7 +229,6 @@
int ownerUid,
int deviceId,
InputController inputController,
- SensorController sensorController,
CameraAccessController cameraAccessController,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -266,15 +257,8 @@
} else {
mInputController = inputController;
}
- if (sensorController == null) {
- mSensorController = new SensorController(mDeviceId, mParams.getVirtualSensorCallback());
- } else {
- mSensorController = sensorController;
- }
- final List<VirtualSensorConfig> virtualSensorConfigs = mParams.getVirtualSensorConfigs();
- for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
- createVirtualSensor(virtualSensorConfigs.get(i));
- }
+ mSensorController = new SensorController(this, mDeviceId,
+ mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
try {
@@ -284,6 +268,11 @@
}
}
+ @VisibleForTesting
+ SensorController getSensorControllerForTest() {
+ return mSensorController;
+ }
+
/**
* Returns the flags that should be added to any virtual displays created on this virtual
* device.
@@ -426,8 +415,6 @@
virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
}
mVirtualDisplays.clear();
- mVirtualSensorList = null;
- mVirtualSensors.clear();
}
// Destroy the display outside locked section.
for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
@@ -723,43 +710,17 @@
}
}
- private void createVirtualSensor(@NonNull VirtualSensorConfig config) {
- final IBinder sensorToken =
- new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
- final long ident = Binder.clearCallingIdentity();
- try {
- int handle = mSensorController.createSensor(sensorToken, config);
- VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
- this, sensorToken);
- synchronized (mVirtualDeviceLock) {
- mVirtualSensors.put(handle, sensor);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
public List<VirtualSensor> getVirtualSensorList() {
super.getVirtualSensorList_enforcePermission();
- synchronized (mVirtualDeviceLock) {
- if (mVirtualSensorList == null) {
- mVirtualSensorList = new ArrayList<>();
- for (int i = 0; i < mVirtualSensors.size(); ++i) {
- mVirtualSensorList.add(mVirtualSensors.valueAt(i));
- }
- mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
- }
- return mVirtualSensorList;
- }
+ return mSensorController.getSensorList();
}
+ @Nullable
VirtualSensor getVirtualSensorByHandle(int handle) {
- synchronized (mVirtualDeviceLock) {
- return mVirtualSensors.get(handle);
- }
+ return mSensorController.getSensorByHandle(handle);
}
@Override // Binder call
@@ -960,7 +921,12 @@
"Virtual device doesn't have a virtual display with ID " + displayId);
}
- releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
index f5e5a43..dd5545d 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
@@ -16,17 +16,15 @@
package com.android.server.contentprotection;
-import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED;
-
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.service.contentcapture.ContentCaptureService;
+import android.service.contentcapture.IContentProtectionService;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureEvent;
-import android.view.contentcapture.IContentCaptureDirectManager;
import com.android.internal.infra.ServiceConnector;
@@ -38,7 +36,7 @@
* @hide
*/
public class RemoteContentProtectionService
- extends ServiceConnector.Impl<IContentCaptureDirectManager> {
+ extends ServiceConnector.Impl<IContentProtectionService> {
private static final String TAG = RemoteContentProtectionService.class.getSimpleName();
@@ -57,7 +55,7 @@
.setComponent(componentName),
bindAllowInstant ? Context.BIND_ALLOW_INSTANT : 0,
userId,
- IContentCaptureDirectManager.Stub::asInterface);
+ IContentProtectionService.Stub::asInterface);
mComponentName = componentName;
}
@@ -68,7 +66,7 @@
@Override // from ServiceConnector.Impl
protected void onServiceConnectionStatusChanged(
- @NonNull IContentCaptureDirectManager service, boolean isConnected) {
+ @NonNull IContentProtectionService service, boolean isConnected) {
Slog.i(
TAG,
"Connection status for: "
@@ -78,9 +76,6 @@
}
public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
- run(
- service ->
- service.sendEvents(
- events, FLUSH_REASON_LOGIN_DETECTED, /* options= */ null));
+ run(service -> service.onLoginDetected(events));
}
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 21a9a9f..1e3a5f4 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -60,6 +60,14 @@
// GWP-ASan's CTS test ensures that recoverable tombstones work,
// which is emitted by the NativeTombstoneManager.
"name": "CtsGwpAsanTestCases"
+ },
+ {
+ "name": "FrameworksVcnTests",
+ "file_patterns": ["VcnManagementService\\.java"]
+ },
+ {
+ "name": "CtsVcnTestCases",
+ "file_patterns": ["VcnManagementService\\.java"]
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index e8c85ce..3efb6c3 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -1065,13 +1065,20 @@
boolean isRestricted = false;
synchronized (mLock) {
final Vcn vcn = mVcns.get(subGrp);
+ final VcnConfig vcnConfig = mConfigs.get(subGrp);
if (vcn != null) {
+ if (vcnConfig == null) {
+ // TODO: b/284381334 Investigate for the root cause of this issue
+ // and handle it properly
+ logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
+ }
+
if (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE) {
isVcnManagedNetwork = true;
}
final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
- subGrp, mLastSnapshot, mConfigs.get(subGrp));
+ subGrp, mLastSnapshot, vcnConfig);
for (int restrictedTransport : restrictedTransports) {
if (ncCopy.hasTransport(restrictedTransport)) {
if (restrictedTransport == TRANSPORT_CELLULAR
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 62651dd..ae12258 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -142,6 +142,7 @@
"android.hardware.camera.provider@2.4::ICameraProvider",
"android.hardware.gnss@1.0::IGnss",
"android.hardware.graphics.allocator@2.0::IAllocator",
+ "android.hardware.graphics.allocator@4.0::IAllocator",
"android.hardware.graphics.composer@2.1::IComposer",
"android.hardware.health@2.0::IHealth",
"android.hardware.light@2.0::ILight",
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1f8a499..9474fc1 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -71,6 +71,7 @@
import android.content.pm.SigningDetails.CertCapabilities;
import android.content.pm.UserInfo;
import android.database.Cursor;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteStatement;
import android.os.Binder;
@@ -1419,7 +1420,13 @@
private void purgeOldGrants(UserAccounts accounts) {
synchronized (accounts.dbLock) {
synchronized (accounts.cacheLock) {
- List<Integer> uids = accounts.accountsDb.findAllUidGrants();
+ List<Integer> uids;
+ try {
+ uids = accounts.accountsDb.findAllUidGrants();
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.w(TAG, "Could not delete grants for user = " + accounts.userId);
+ return;
+ }
for (int uid : uids) {
final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
if (packageExists) {
@@ -1445,7 +1452,13 @@
mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
} catch (NameNotFoundException e) {
// package does not exist - remove visibility values
- accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+ try {
+ accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+ } catch (SQLiteCantOpenDatabaseException sqlException) {
+ Log.w(TAG, "Could not delete account visibility for user = "
+ + accounts.userId, sqlException);
+ continue;
+ }
synchronized (accounts.dbLock) {
synchronized (accounts.cacheLock) {
for (Account account : accounts.visibilityCache.keySet()) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2d27533..12de0d7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -621,7 +621,8 @@
try {
final ServiceRecord.StartItem si = r.pendingStarts.get(0);
startServiceInnerLocked(this, si.intent, r, false, true, si.callingId,
- si.mCallingProcessName, r.startRequested, si.mCallingPackageName);
+ si.mCallingProcessName, si.mCallingProcessState,
+ r.startRequested, si.mCallingPackageName);
} catch (TransactionTooLargeException e) {
// Ignore, nobody upstack cares.
}
@@ -977,10 +978,22 @@
fgRequired = false;
}
+ final ProcessRecord callingApp;
+ synchronized (mAm.mPidsSelfLocked) {
+ callingApp = mAm.mPidsSelfLocked.get(callingPid);
+ }
+ final String callingProcessName = callingApp != null
+ ? callingApp.processName : callingPackage;
+ final int callingProcessState =
+ callingApp != null && callingApp.getThread() != null && !callingApp.isKilled()
+ ? callingApp.mState.getCurProcState() : ActivityManager.PROCESS_STATE_UNKNOWN;
+ r.updateProcessStateOnRequest();
+
// The package could be frozen (meaning it's doing surgery), defer the actual
// start until the package is unfrozen.
if (deferServiceBringupIfFrozenLocked(r, service, callingPackage, callingFeatureId,
- callingUid, callingPid, fgRequired, callerFg, userId,
+ callingUid, callingPid, callingProcessName,
+ callingProcessState, fgRequired, callerFg, userId,
backgroundStartPrivileges, false, null)) {
return null;
}
@@ -1001,7 +1014,7 @@
// what realResult contains.
final ComponentName realResult =
startServiceInnerLocked(r, service, callingUid, callingPid,
- getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
+ callingProcessName, callingProcessState,
fgRequired, callerFg,
backgroundStartPrivileges, callingPackage);
if (res.aliasComponent != null
@@ -1013,17 +1026,9 @@
}
}
- private String getCallingProcessNameLocked(int callingUid, int callingPid,
- String callingPackage) {
- synchronized (mAm.mPidsSelfLocked) {
- final ProcessRecord callingApp = mAm.mPidsSelfLocked.get(callingPid);
- return callingApp != null ? callingApp.processName : callingPackage;
- }
- }
-
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
- int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
- boolean callerFg,
+ int callingUid, int callingPid, String callingProcessName,
+ int callingProcessState, boolean fgRequired, boolean callerFg,
BackgroundStartPrivileges backgroundStartPrivileges, String callingPackage)
throws TransactionTooLargeException {
NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
@@ -1037,7 +1042,8 @@
r.delayedStop = false;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- service, neededGrants, callingUid, callingProcessName, callingPackage));
+ service, neededGrants, callingUid, callingProcessName, callingPackage,
+ callingProcessState));
// We want to allow scheduling user-initiated jobs when the app is running a
// foreground service that was started in the same conditions that allows for scheduling
@@ -1140,7 +1146,8 @@
r.allowBgActivityStartsOnServiceStart(backgroundStartPrivileges);
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
- callingUid, callingProcessName, wasStartRequested, callingPackage);
+ callingUid, callingProcessName, callingProcessState,
+ wasStartRequested, callingPackage);
return cmp;
}
@@ -1244,7 +1251,8 @@
@GuardedBy("mAm")
private boolean deferServiceBringupIfFrozenLocked(ServiceRecord s, Intent serviceIntent,
String callingPackage, @Nullable String callingFeatureId,
- int callingUid, int callingPid, boolean fgRequired, boolean callerFg, int userId,
+ int callingUid, int callingPid, String callingProcessName,
+ int callingProcessState, boolean fgRequired, boolean callerFg, int userId,
BackgroundStartPrivileges backgroundStartPrivileges,
boolean isBinding, IServiceConnection connection) {
final PackageManagerInternal pm = mAm.getPackageManagerInternal();
@@ -1258,8 +1266,6 @@
curPendingBringups = new ArrayList<>();
mPendingBringups.put(s, curPendingBringups);
}
- final String callingProcessName = getCallingProcessNameLocked(
- callingUid, callingPid, callingPackage);
curPendingBringups.add(new Runnable() {
@Override
public void run() {
@@ -1291,7 +1297,7 @@
} else { // Starting a service
try {
startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
- callingProcessName, fgRequired, callerFg,
+ callingProcessName, callingProcessState, fgRequired, callerFg,
backgroundStartPrivileges, callingPackage);
} catch (TransactionTooLargeException e) {
/* ignore - local call */
@@ -1338,7 +1344,8 @@
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName,
- boolean wasStartRequested, String callingPackage) throws TransactionTooLargeException {
+ int callingProcessState, boolean wasStartRequested, String callingPackage)
+ throws TransactionTooLargeException {
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1381,7 +1388,9 @@
getShortServiceNameForStats(r),
packageState,
packageName,
- callingPackage);
+ callingPackage,
+ callingProcessState,
+ r.mProcessStateOnRequest);
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
@@ -3611,11 +3620,22 @@
return 0;
}
+ final ProcessRecord callingApp;
+ synchronized (mAm.mPidsSelfLocked) {
+ callingApp = mAm.mPidsSelfLocked.get(callingPid);
+ }
+ final String callingProcessName = callingApp != null
+ ? callingApp.processName : callingPackage;
+ final int callingProcessState =
+ callingApp != null && callingApp.getThread() != null && !callingApp.isKilled()
+ ? callingApp.mState.getCurProcState() : ActivityManager.PROCESS_STATE_UNKNOWN;
+ s.updateProcessStateOnRequest();
+
// The package could be frozen (meaning it's doing surgery), defer the actual
// binding until the package is unfrozen.
boolean packageFrozen = deferServiceBringupIfFrozenLocked(s, service, callingPackage, null,
- callingUid, callingPid, false, callerFg, userId, BackgroundStartPrivileges.NONE,
- true, connection);
+ callingUid, callingPid, callingProcessName, callingProcessState,
+ false, callerFg, userId, BackgroundStartPrivileges.NONE, true, connection);
// If permissions need a review before any of the app components can run,
// we schedule binding to the service but do not start its process, then
@@ -3756,7 +3776,9 @@
getShortServiceNameForStats(s),
packageState,
s.packageName,
- callerApp.info.packageName);
+ callerApp.info.packageName,
+ callerApp.mState.getCurProcState(),
+ s.mProcessStateOnRequest);
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
+ ": received=" + b.intent.received
@@ -5355,7 +5377,7 @@
// be called.
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- null, null, 0, null, null));
+ null, null, 0, null, null, ActivityManager.PROCESS_STATE_UNKNOWN));
}
sendServiceArgsLocked(r, execInFg, true);
@@ -6351,7 +6373,8 @@
stopServiceLocked(sr, true);
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
- sr.getLastStartId(), baseIntent, null, 0, null, null));
+ sr.getLastStartId(), baseIntent, null, 0, null, null,
+ ActivityManager.PROCESS_STATE_UNKNOWN));
if (sr.app != null && sr.app.getThread() != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ee77914..c2a2423 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1680,9 +1680,9 @@
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME,
DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS);
- if (mKillBgRestrictedAndCachedIdleSettleTimeMs != currentSettleTime) {
- mService.mHandler.removeMessages(
- ActivityManagerService.IDLE_UIDS_MSG);
+ if (mKillBgRestrictedAndCachedIdleSettleTimeMs < currentSettleTime) {
+ // Don't remove existing messages in case other IDLE_UIDS_MSG initiators use lower
+ // delays, but send a new message if the settle time has decreased.
mService.mHandler.sendEmptyMessageDelayed(
ActivityManagerService.IDLE_UIDS_MSG,
mKillBgRestrictedAndCachedIdleSettleTimeMs);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 14f0849..fb82490 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1176,20 +1176,24 @@
public Intent intent;
public boolean deferUntilActive;
public int originalCallingUid;
+ /** The snapshot process state of the app who sent this broadcast */
+ public int originalCallingAppProcessState;
public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
- int originalCallingUid) {
+ int originalCallingUid, int originalCallingAppProcessState) {
final StickyBroadcast b = new StickyBroadcast();
b.intent = intent;
b.deferUntilActive = deferUntilActive;
b.originalCallingUid = originalCallingUid;
+ b.originalCallingAppProcessState = originalCallingAppProcessState;
return b;
}
@Override
public String toString() {
return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
- + originalCallingUid + "}";
+ + originalCallingUid + ", originalCallingAppProcessState="
+ + originalCallingAppProcessState + "}";
}
}
@@ -1522,6 +1526,8 @@
*/
int mBootPhase;
+ volatile boolean mDeterministicUidIdle = false;
+
@VisibleForTesting
public WindowManagerService mWindowManager;
WindowManagerInternal mWmInternal;
@@ -1616,6 +1622,8 @@
static final int SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG = 77;
static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 78;
static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
+ static final int ADD_UID_TO_OBSERVER_MSG = 80;
+ static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1774,6 +1782,12 @@
case PUSH_TEMP_ALLOWLIST_UI_MSG: {
pushTempAllowlist();
} break;
+ case ADD_UID_TO_OBSERVER_MSG: {
+ mUidObserverController.addUidToObserverImpl((IBinder) msg.obj, msg.arg1);
+ } break;
+ case REMOVE_UID_FROM_OBSERVER_MSG: {
+ mUidObserverController.removeUidFromObserverImpl((IBinder) msg.obj, msg.arg1);
+ } break;
}
}
}
@@ -14048,7 +14062,8 @@
receivers, null, null, 0, null, null, false, true, true, -1,
originalStickyCallingUid, BackgroundStartPrivileges.NONE,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
- null /* filterExtrasForReceiver */);
+ null /* filterExtrasForReceiver */,
+ broadcast.originalCallingAppProcessState);
queue.enqueueBroadcastLocked(r);
}
}
@@ -14863,6 +14878,7 @@
}
}
+ final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
// Add to the sticky list if requested.
if (sticky) {
if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
@@ -14925,12 +14941,13 @@
if (intent.filterEquals(list.get(i).intent)) {
// This sticky already exists, replace it.
list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
- callingUid));
+ callingUid, callerAppProcessState));
break;
}
}
if (i >= stickiesCount) {
- list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive, callingUid));
+ list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive, callingUid,
+ callerAppProcessState));
}
}
@@ -15013,7 +15030,8 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
registeredReceivers, resultToApp, resultTo, resultCode, resultData,
resultExtras, ordered, sticky, false, userId,
- backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver);
+ backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+ callerAppProcessState);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
queue.enqueueBroadcastLocked(r);
registeredReceivers = null;
@@ -15107,7 +15125,8 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId,
- backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver);
+ backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+ callerAppProcessState);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
queue.enqueueBroadcastLocked(r);
@@ -15124,6 +15143,19 @@
return ActivityManager.BROADCAST_SUCCESS;
}
+ @GuardedBy("this")
+ private int getRealProcessStateLocked(ProcessRecord app, int pid) {
+ if (app == null) {
+ synchronized (mPidsSelfLocked) {
+ app = mPidsSelfLocked.get(pid);
+ }
+ }
+ if (app != null && app.getThread() != null && !app.isKilled()) {
+ return app.mState.getCurProcState();
+ }
+ return PROCESS_STATE_NONEXISTENT;
+ }
+
@VisibleForTesting
ArrayList<StickyBroadcast> getStickyBroadcasts(String action, int userId) {
final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
@@ -16478,6 +16510,11 @@
}
}
+ @Override
+ public void setDeterministicUidIdle(boolean deterministic) {
+ mDeterministicUidIdle = deterministic;
+ }
+
/** Make the currently active UIDs idle after a certain grace period. */
final void idleUids() {
synchronized (this) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d61c3f7..e701126 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -293,6 +293,8 @@
return runKillAll(pw);
case "make-uid-idle":
return runMakeIdle(pw);
+ case "set-deterministic-uid-idle":
+ return runSetDeterministicUidIdle(pw);
case "monitor":
return runMonitor(pw);
case "watch-uids":
@@ -1523,6 +1525,23 @@
return 0;
}
+ int runSetDeterministicUidIdle(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ boolean deterministic = Boolean.parseBoolean(getNextArgRequired());
+ mInterface.setDeterministicUidIdle(deterministic);
+ return 0;
+ }
+
static final class MyActivityController extends IActivityController.Stub {
final IActivityManager mInterface;
final PrintWriter mPw;
@@ -4274,6 +4293,11 @@
pw.println(" make-uid-idle [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" If the given application's uid is in the background and waiting to");
pw.println(" become idle (not allowing background services), do that now.");
+ pw.println(
+ " set-deterministic-uid-idle [--user <USER_ID> | all | current] <true|false>");
+ pw.println(" If true, sets the timing of making UIDs idle consistent and");
+ pw.println(" deterministic. If false, the timing will be variable depending on");
+ pw.println(" other activity on the device. The default is false.");
pw.println(" monitor [--gdb <port>] [-p <TARGET>] [-s] [-c] [-k]");
pw.println(" Start monitoring for crashes or ANRs.");
pw.println(" --gdb: start gdbserv on the given port at crash/ANR");
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 4b6d324..a80ad59 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -106,6 +106,12 @@
long lastCpuDelayTime;
/**
+ * Snapshotted value of {@link ProcessStateRecord#getCurProcState()} before
+ * dispatching the current broadcast to the receiver in this process.
+ */
+ int lastProcessState;
+
+ /**
* Ordered collection of broadcasts that are waiting to be dispatched to
* this process, as a pair of {@link BroadcastRecord} and the index into
* {@link BroadcastRecord#receivers} that represents the receiver.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 8688f25..f13dc89 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -269,7 +269,10 @@
Activity.RESULT_CANCELED, null, null,
false, false, oldRecord.shareIdentity, oldRecord.userId,
oldRecord.callingUid, r.callingUid, r.callerPackage,
- SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0);
+ SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
+ oldRecord.resultToApp != null
+ ? oldRecord.resultToApp.mState.getCurProcState()
+ : ActivityManager.PROCESS_STATE_UNKNOWN);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
+ mQueueName + "] sending broadcast result of "
@@ -367,6 +370,7 @@
}
r.curApp = app;
+ r.curAppLastProcessState = app.mState.getCurProcState();
final ProcessReceiverRecord prr = app.mReceivers;
prr.addCurReceiver(r);
app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
@@ -418,6 +422,7 @@
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + ": NOT STARTED!");
r.curApp = null;
+ r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
prr.removeCurReceiver(r);
}
}
@@ -620,7 +625,8 @@
r.getDeliveryGroupPolicy(),
r.intent.getFlags(),
BroadcastRecord.getReceiverPriority(curReceiver),
- r.callerProcState);
+ r.callerProcState,
+ r.curAppLastProcessState);
}
if (state == BroadcastRecord.IDLE) {
Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -682,6 +688,7 @@
r.curFilter = null;
r.curReceiver = null;
r.curApp = null;
+ r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
r.curFilteredExtras = null;
r.mWasReceiverAppStopped = false;
mPendingBroadcast = null;
@@ -751,7 +758,8 @@
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
int receiverUid, int callingUid, String callingPackage,
- long dispatchDelay, long receiveDelay, int priority) throws RemoteException {
+ long dispatchDelay, long receiveDelay, int priority,
+ int receiverProcessState) throws RemoteException {
// If the broadcaster opted-in to sharing their identity, then expose package visibility for
// the receiver.
if (shareIdentity) {
@@ -802,7 +810,7 @@
SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
app != null ? app.info.packageName : null, callingPackage,
r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
- priority, r.callerProcState);
+ priority, r.callerProcState, receiverProcessState);
}
}
@@ -849,6 +857,7 @@
// things that directly call the IActivityManager API, which
// are already core system stuff so don't matter for this.
r.curApp = filter.receiverList.app;
+ r.curAppLastProcessState = r.curApp.mState.getCurProcState();
filter.receiverList.app.mReceivers.addCurReceiver(r);
mService.enqueueOomAdjTargetLocked(r.curApp);
mService.updateOomAdjPendingTargetsLocked(
@@ -883,7 +892,10 @@
r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
filter.receiverList.uid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime, filter.getPriority());
+ r.receiverTime - r.dispatchTime, filter.getPriority(),
+ filter.receiverList.app != null
+ ? filter.receiverList.app.mState.getCurProcState()
+ : ActivityManager.PROCESS_STATE_UNKNOWN);
// parallel broadcasts are fire-and-forget, not bookended by a call to
// finishReceiverLocked(), so we manage their activity-start token here
if (filter.receiverList.app != null
@@ -1174,7 +1186,10 @@
r.resultData, r.resultExtras, false, false, r.shareIdentity,
r.userId, r.callingUid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
- now - r.dispatchTime, 0);
+ now - r.dispatchTime, 0,
+ r.resultToApp != null
+ ? r.resultToApp.mState.getCurProcState()
+ : ActivityManager.PROCESS_STATE_UNKNOWN);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
// Set this to null so that the reference
// (local and remote) isn't kept in the mBroadcastHistory.
@@ -1480,6 +1495,7 @@
r.intent.getAction(), r.getHostingRecordTriggerType()),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
+ r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
if (r.curApp == null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 75dcabf..beba961 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -33,6 +33,7 @@
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.getReceiverClassName;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
@@ -68,6 +69,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.MathUtils;
@@ -213,6 +215,13 @@
new AtomicReference<>();
/**
+ * Container for holding the set of broadcast records that satisfied a certain criteria.
+ */
+ @GuardedBy("mService")
+ private final AtomicReference<ArrayMap<BroadcastRecord, Boolean>> mRecordsLookupCache =
+ new AtomicReference<>();
+
+ /**
* Map from UID to its last known "foreground" state. A UID is considered to be in
* "foreground" state when it's procState is {@link ActivityManager#PROCESS_STATE_TOP}.
* <p>
@@ -742,13 +751,16 @@
broadcastConsumer = mBroadcastConsumerSkipAndCanceled;
break;
case BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED:
+ // TODO: Allow applying MERGED policy for broadcasts with more than one receiver.
+ if (r.receivers.size() > 1) {
+ return;
+ }
final BundleMerger extrasMerger = r.options.getDeliveryGroupExtrasMerger();
if (extrasMerger == null) {
// Extras merger is required to be able to merge the extras. So, if it's not
// supplied, then ignore the delivery group policy.
return;
}
- // TODO: Don't merge with the same BroadcastRecord more than once.
broadcastConsumer = (record, recordIndex) -> {
r.intent.mergeExtras(record.intent, extrasMerger);
mBroadcastConsumerSkipAndCanceled.accept(record, recordIndex);
@@ -758,6 +770,7 @@
logw("Unknown delivery group policy: " + policy);
return;
}
+ final ArrayMap<BroadcastRecord, Boolean> recordsLookupCache = getRecordsLookupCache();
forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
// If the receiver is already in a terminal state, then ignore it.
if (isDeliveryStateTerminal(testRecord.getDeliveryState(testIndex))) {
@@ -769,22 +782,44 @@
|| !r.matchesDeliveryGroup(testRecord)) {
return false;
}
- // TODO: If a process is in a deferred state, we can always apply the policy as long
- // as it is one of the receivers for the new broadcast.
// For ordered broadcast, check if the receivers for the new broadcast is a superset
// of those for the previous one as skipping and removing only one of them could result
// in an inconsistent state.
- if (testRecord.ordered || testRecord.resultTo != null) {
- // TODO: Cache this result in some way so that we don't have to perform the
- // same check for all the broadcast receivers.
- return r.containsAllReceivers(testRecord.receivers);
- } else if (testRecord.prioritized) {
- return r.containsAllReceivers(testRecord.receivers);
+ if (testRecord.ordered || testRecord.prioritized) {
+ return containsAllReceivers(r, testRecord, recordsLookupCache);
+ } else if (testRecord.resultTo != null) {
+ return testRecord.getDeliveryState(testIndex) == DELIVERY_DEFERRED
+ ? r.containsReceiver(testRecord.receivers.get(testIndex))
+ : containsAllReceivers(r, testRecord, recordsLookupCache);
} else {
return r.containsReceiver(testRecord.receivers.get(testIndex));
}
}, broadcastConsumer, true);
+ recordsLookupCache.clear();
+ mRecordsLookupCache.compareAndSet(null, recordsLookupCache);
+ }
+
+ @NonNull
+ private ArrayMap<BroadcastRecord, Boolean> getRecordsLookupCache() {
+ ArrayMap<BroadcastRecord, Boolean> recordsLookupCache =
+ mRecordsLookupCache.getAndSet(null);
+ if (recordsLookupCache == null) {
+ recordsLookupCache = new ArrayMap<>();
+ }
+ return recordsLookupCache;
+ }
+
+ private boolean containsAllReceivers(@NonNull BroadcastRecord record,
+ @NonNull BroadcastRecord testRecord,
+ @NonNull ArrayMap<BroadcastRecord, Boolean> recordsLookupCache) {
+ final int idx = recordsLookupCache.indexOfKey(testRecord);
+ if (idx > 0) {
+ return recordsLookupCache.valueAt(idx);
+ }
+ final boolean containsAll = record.containsAllReceivers(testRecord.receivers);
+ recordsLookupCache.put(testRecord, containsAll);
+ return containsAll;
}
/**
@@ -1002,6 +1037,7 @@
mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
UserHandle.getAppId(app.uid), r.callingUid, true);
}
+ queue.lastProcessState = app.mState.getCurProcState();
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
thread.scheduleRegisteredReceiver(
@@ -1916,12 +1952,16 @@
? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
: BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
final int type;
+ final int receiverProcessState;
if (queue == null) {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
+ receiverProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
} else if (queue.getActiveViaColdStart()) {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+ receiverProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
} else {
type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+ receiverProcessState = queue.lastProcessState;
}
// With the new per-process queues, there's no delay between being
// "dispatched" and "scheduled", so we report no "receive delay"
@@ -1936,7 +1976,8 @@
receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
app != null ? app.info.packageName : null, r.callerPackage,
r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
- BroadcastRecord.getReceiverPriority(receiver), r.callerProcState);
+ BroadcastRecord.getReceiverPriority(receiver), r.callerProcState,
+ receiverProcessState);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 67d43fd..198adcb 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,7 +44,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
-import android.app.ActivityManager;
import android.app.ActivityManager.ProcessState;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
@@ -271,6 +270,8 @@
BroadcastFilter curFilter; // the registered receiver currently running.
Bundle curFilteredExtras; // the bundle that has been filtered by the package visibility rules
+ int curAppLastProcessState; // The last process state of the current receiver before receiving
+
boolean mIsReceiverAppRunning; // Was the receiver's app already running.
boolean mWasReceiverAppStopped; // Was the receiver app stopped prior to starting
@@ -432,13 +433,14 @@
boolean initialSticky, int userId,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
- @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ int callerAppProcessState) {
this(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid,
callingUid, callerInstantApp, resolvedType, requiredPermissions,
excludedPermissions, excludedPackages, appOp, options, receivers, resultToApp,
resultTo, resultCode, resultData, resultExtras, serialized, sticky,
initialSticky, userId, -1, backgroundStartPrivileges, timeoutExempt,
- filterExtrasForReceiver);
+ filterExtrasForReceiver, callerAppProcessState);
}
BroadcastRecord(BroadcastQueue _queue,
@@ -453,7 +455,8 @@
boolean _initialSticky, int _userId, int originalStickyCallingUid,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
- @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ int callerAppProcessState) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -465,8 +468,7 @@
callerFeatureId = _callerFeatureId;
callingPid = _callingPid;
callingUid = _callingUid;
- callerProcState = callerApp == null ? ActivityManager.PROCESS_STATE_UNKNOWN
- : callerApp.getCurProcState();
+ callerProcState = callerAppProcessState;
callerInstantApp = _callerInstantApp;
callerInstrumented = isCallerInstrumented(_callerApp, _callingUid);
resolvedType = _resolvedType;
@@ -606,7 +608,8 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
splitReceivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, initialSticky, userId,
- mBackgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver);
+ mBackgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+ callerProcState);
split.enqueueTime = this.enqueueTime;
split.enqueueRealTime = this.enqueueRealTime;
split.enqueueClockTime = this.enqueueClockTime;
@@ -686,7 +689,7 @@
uid2receiverList.valueAt(i), null /* _resultToApp */, null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
mBackgroundStartPrivileges, timeoutExempt,
- filterExtrasForReceiver);
+ filterExtrasForReceiver, callerProcState);
br.enqueueTime = this.enqueueTime;
br.enqueueRealTime = this.enqueueRealTime;
br.enqueueClockTime = this.enqueueClockTime;
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 695ea4f..3ab40d2 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -246,9 +246,13 @@
}
}
+ final int callingProcessState = r != null
+ ? r.mState.getCurProcState() : ActivityManager.PROCESS_STATE_UNKNOWN;
+
if (providerRunning) {
cpi = cpr.info;
+
if (r != null && cpr.canRunHere(r)) {
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
cpr.name.flattenToShortString(), startTime);
@@ -265,7 +269,8 @@
r.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- cpi.packageName, callingPackage);
+ cpi.packageName, callingPackage,
+ callingProcessState, callingProcessState);
return holder;
}
@@ -281,6 +286,8 @@
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
cpr.name.flattenToShortString(), startTime);
+ final int providerProcessState = cpr.proc.mState.getCurProcState();
+
final long origId = Binder.clearCallingIdentity();
try {
checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");
@@ -337,7 +344,8 @@
cpr.proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- cpi.packageName, callingPackage);
+ cpi.packageName, callingPackage,
+ callingProcessState, providerProcessState);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -514,7 +522,8 @@
proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- cpi.packageName, callingPackage);
+ cpi.packageName, callingPackage,
+ callingProcessState, proc.mState.getCurProcState());
} else {
final int packageState =
((cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0)
@@ -539,7 +548,8 @@
PROVIDER_ACQUISITION_EVENT_REPORTED,
proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
- packageState, cpi.packageName, callingPackage);
+ packageState, cpi.packageName, callingPackage,
+ callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a86c2e3..764bbe8 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1471,7 +1471,8 @@
if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
|| uidRec.isSetAllowListed()) {
uidRec.setLastBackgroundTime(nowElapsed);
- if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+ if (mService.mDeterministicUidIdle
+ || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
// Note: the background settle time is in elapsed realtime, while
// the handler time base is uptime. All this means is that we may
// stop background uids later than we had intended, but that only
@@ -3227,7 +3228,8 @@
// (for states debouncing to avoid from thrashing).
state.setLastCanKillOnBgRestrictedAndIdleTime(nowElapsed);
// Kick off the delayed checkup message if needed.
- if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+ if (mService.mDeterministicUidIdle
+ || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs);
}
@@ -3346,6 +3348,7 @@
@GuardedBy("mService")
void idleUidsLocked() {
final int N = mActiveUids.size();
+ mService.mHandler.removeMessages(IDLE_UIDS_MSG);
if (N <= 0) {
return;
}
@@ -3391,7 +3394,6 @@
}
}
if (nextTime > 0) {
- mService.mHandler.removeMessages(IDLE_UIDS_MSG);
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4342cb9..c5776d8 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2572,9 +2572,14 @@
// and did the cleanup before the actual death notification. Check the dying processes.
predecessor = mDyingProcesses.get(processName, info.uid);
if (predecessor != null) {
- if (app != null) {
+ // The process record could have existed but its pid is set to 0. In this case,
+ // the 'app' and 'predecessor' could end up pointing to the same instance;
+ // so make sure we check this case here.
+ if (app != null && app != predecessor) {
app.mPredecessor = predecessor;
predecessor.mSuccessor = app;
+ } else {
+ app = null;
}
Slog.w(TAG_PROCESSES, predecessor.toString() + " is attached to a previous process "
+ predecessor.getDyingPid());
@@ -5195,6 +5200,8 @@
mDyingProcesses.remove(app.processName, app.uid);
app.setDyingPid(0);
handlePrecedingAppDiedLocked(app);
+ // Remove from the LRU list if it's still there.
+ removeLruProcessLocked(app);
return true;
}
return false;
@@ -5243,7 +5250,9 @@
mAppsInBackgroundRestricted.add(app);
final long future = killAppIfBgRestrictedAndCachedIdleLocked(
app, nowElapsed);
- if (future > 0 && !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+ if (future > 0
+ && (mService.mDeterministicUidIdle
+ || !mService.mHandler.hasMessages(IDLE_UIDS_MSG))) {
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
future - nowElapsed);
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index dccbb0a..50fe6d7 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
@@ -244,6 +245,11 @@
*/
long mRestartSchedulingTime;
+ /**
+ * The snapshot process state when the service is requested (either start or bind).
+ */
+ int mProcessStateOnRequest;
+
static class StartItem {
final ServiceRecord sr;
final boolean taskRemoved;
@@ -253,6 +259,7 @@
final Intent intent;
final NeededUriGrants neededGrants;
final @Nullable String mCallingPackageName;
+ final int mCallingProcessState;
long deliveredTime;
int deliveryCount;
int doneExecutingCount;
@@ -262,7 +269,8 @@
StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id,
Intent _intent, NeededUriGrants _neededGrants, int _callingId,
- String callingProcessName, @Nullable String callingPackageName) {
+ String callingProcessName, @Nullable String callingPackageName,
+ int callingProcessState) {
sr = _sr;
taskRemoved = _taskRemoved;
id = _id;
@@ -271,6 +279,7 @@
callingId = _callingId;
mCallingProcessName = callingProcessName;
mCallingPackageName = callingPackageName;
+ mCallingProcessState = callingProcessState;
}
UriPermissionOwner getUriPermissionsLocked() {
@@ -873,6 +882,7 @@
app.mServices.updateHostingComonentTypeForBindingsLocked();
}
app = proc;
+ updateProcessStateOnRequest();
if (pendingConnectionGroup > 0 && proc != null) {
final ProcessServiceRecord psr = proc.mServices;
psr.setConnectionService(this);
@@ -899,6 +909,11 @@
}
}
+ void updateProcessStateOnRequest() {
+ mProcessStateOnRequest = app != null && app.getThread() != null && !app.isKilled()
+ ? app.mState.getCurProcState() : PROCESS_STATE_NONEXISTENT;
+ }
+
@NonNull
ArrayMap<IBinder, ArrayList<ConnectionRecord>> getConnections() {
return connections;
@@ -1061,12 +1076,13 @@
if (app == null) {
return;
}
- if (mBackgroundStartPrivilegesByStartMerged.allowsAny()
- || mIsAllowedBgActivityStartsByBinding) {
+ BackgroundStartPrivileges backgroundStartPrivileges =
+ getBackgroundStartPrivilegesWithExclusiveToken();
+ if (backgroundStartPrivileges.allowsAny()) {
// if the token is already there it's safe to "re-add it" - we're dealing with
// a set of Binder objects
app.addOrUpdateBackgroundStartPrivileges(this,
- getBackgroundStartPrivilegesWithExclusiveToken());
+ backgroundStartPrivileges);
} else {
app.removeBackgroundStartPrivileges(this);
}
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index a6677a5..7eeec32 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -30,6 +30,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -104,40 +105,62 @@
}
}
- void addUidToObserver(@NonNull IBinder observerToken, int uid) {
- synchronized (mLock) {
- int i = mUidObservers.beginBroadcast();
- while (i-- > 0) {
- var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
- if (reg.getToken().equals(observerToken)) {
- reg.addUid(uid);
- break;
- }
-
- if (i == 0) {
- Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
- }
- }
- mUidObservers.finishBroadcast();
- }
+ final void addUidToObserver(@NonNull IBinder observerToken, int uid) {
+ Message msg = Message.obtain(mHandler, ActivityManagerService.ADD_UID_TO_OBSERVER_MSG,
+ uid, /*arg2*/ 0, observerToken);
+ mHandler.sendMessage(msg);
}
- void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
- synchronized (mLock) {
- int i = mUidObservers.beginBroadcast();
- while (i-- > 0) {
- var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
- if (reg.getToken().equals(observerToken)) {
- reg.removeUid(uid);
- break;
- }
-
- if (i == 0) {
- Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
- }
+ /**
+ * Add a uid to the list of uids an observer is interested in. Must be run on the same thread
+ * as mDispatchRunnable.
+ *
+ * @param observerToken The token identifier for a UidObserver
+ * @param uid The uid to add to the list of watched uids
+ */
+ public final void addUidToObserverImpl(@NonNull IBinder observerToken, int uid) {
+ int i = mUidObservers.beginBroadcast();
+ while (i-- > 0) {
+ var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+ if (reg.getToken().equals(observerToken)) {
+ reg.addUid(uid);
+ break;
}
- mUidObservers.finishBroadcast();
+
+ if (i == 0) {
+ Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
+ }
}
+ mUidObservers.finishBroadcast();
+ }
+
+ final void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
+ Message msg = Message.obtain(mHandler, ActivityManagerService.REMOVE_UID_FROM_OBSERVER_MSG,
+ uid, /*arg2*/ 0, observerToken);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Remove a uid from the list of uids an observer is interested in. Must be run on the same
+ * thread as mDispatchRunnable.
+ *
+ * @param observerToken The token identifier for a UidObserver
+ * @param uid The uid to remove from the list of watched uids
+ */
+ public final void removeUidFromObserverImpl(@NonNull IBinder observerToken, int uid) {
+ int i = mUidObservers.beginBroadcast();
+ while (i-- > 0) {
+ var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+ if (reg.getToken().equals(observerToken)) {
+ reg.removeUid(uid);
+ break;
+ }
+
+ if (i == 0) {
+ Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
+ }
+ }
+ mUidObservers.finishBroadcast();
}
int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 0a44da3..1dff62e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -924,9 +924,14 @@
mSwitchedOps.put(switchCode,
ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
}
- mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
- new AppOpsCheckingServiceImpl(
- storageFile, this, handler, context, mSwitchedOps));
+ if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+ mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
+ LocalServices.getService(AppOpsCheckingServiceInterface.class));
+ } else {
+ mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
+ new AppOpsCheckingServiceImpl(storageFile, this, handler, context,
+ mSwitchedOps));
+ }
mAppOpsCheckingService.addAppOpsModeChangedListener(
new AppOpsCheckingServiceInterface.AppOpsModeChangedListener() {
@Override
@@ -944,8 +949,6 @@
packageName, code, mode, userId));
}
});
- //mAppOpsCheckingService = new AppOpsCheckingServiceLoggingDecorator(
- // LocalServices.getService(AppOpsCheckingServiceInterface.class));
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
code -> notifyWatchersOfChange(code, UID_ANY));
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 8ef2a1b..cb5e7f1 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,7 +21,10 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static com.android.server.biometrics.BiometricSensor.STATE_CANCELING;
+import static com.android.server.biometrics.BiometricSensor.STATE_UNKNOWN;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -439,6 +442,13 @@
return false;
}
+ final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ if (errorLockout) {
+ cancelAllSensors(sensor -> Utils.isAtLeastStrength(sensorIdToStrength(sensorId),
+ sensor.getCurrentStrength()));
+ }
+
mErrorEscrow = error;
mVendorCodeEscrow = vendorCode;
@@ -477,8 +487,6 @@
case STATE_AUTH_STARTED:
case STATE_AUTH_STARTED_UI_SHOWING: {
- final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
- || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (isAllowDeviceCredential() && errorLockout) {
// SystemUI handles transition from biometric to device credential.
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
@@ -675,7 +683,9 @@
}
private boolean pauseSensorIfSupported(int sensorId) {
- if (sensorIdToModality(sensorId) == TYPE_FACE) {
+ boolean isSensorCancelling = sensorIdToState(sensorId) == STATE_CANCELING;
+ // If the sensor is locked out, canceling sensors operation is handled in onErrorReceived()
+ if (sensorIdToModality(sensorId) == TYPE_FACE && !isSensorCancelling) {
cancelAllSensors(sensor -> sensor.id == sensorId);
return true;
}
@@ -948,6 +958,27 @@
return TYPE_NONE;
}
+ private @BiometricSensor.SensorState int sensorIdToState(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getSensorState();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return STATE_UNKNOWN;
+ }
+
+ @BiometricManager.Authenticators.Types
+ private int sensorIdToStrength(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getCurrentStrength();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return BIOMETRIC_CONVENIENCE;
+ }
+
private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
final @Modality int modality = sensorIdToModality(sensorId);
switch (modality) {
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 12813c8..47cde15 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -20,6 +20,7 @@
import android.os.IBinder;
import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
class BrightnessRangeController {
@@ -43,20 +44,10 @@
}
void onAmbientLuxChange(float ambientLux) {
- if (NBM_FEATURE_FLAG) {
- boolean nbmTransitionChanged = mNormalBrightnessModeController.onAmbientLuxChange(
- ambientLux);
- int previousHbm = mHbmController.getHighBrightnessMode();
- mHbmController.onAmbientLuxChange(ambientLux);
- int nextHbm = mHbmController.getHighBrightnessMode();
- // if hbm changed - callback was triggered in mHbmController.onAmbientLuxChange
- // if nbm transition not changed - no need to trigger callback
- if (previousHbm == nextHbm && nbmTransitionChanged) {
- mModeChangeCallback.run();
- }
- } else {
- mHbmController.onAmbientLuxChange(ambientLux);
- }
+ applyChanges(
+ () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux),
+ () -> mHbmController.onAmbientLuxChange(ambientLux)
+ );
}
float getNormalBrightnessMax() {
@@ -65,10 +56,16 @@
void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token,
DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) {
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
- mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
- displayDeviceConfig.getHighBrightnessModeData(),
- displayDeviceConfig::getHdrBrightnessFromSdr);
+ applyChanges(
+ () -> mNormalBrightnessModeController.resetNbmData(
+ displayDeviceConfig.getLuxThrottlingData()),
+ () -> {
+ mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
+ mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+ displayDeviceConfig.getHighBrightnessModeData(),
+ displayDeviceConfig::getHdrBrightnessFromSdr);
+ }
+ );
}
void stop() {
@@ -76,7 +73,10 @@
}
void setAutoBrightnessEnabled(int state) {
- mHbmController.setAutoBrightnessEnabled(state);
+ applyChanges(
+ () -> mNormalBrightnessModeController.setAutoBrightnessState(state),
+ () -> mHbmController.setAutoBrightnessEnabled(state)
+ );
}
void onBrightnessChanged(float brightness, float unthrottledBrightness,
@@ -109,4 +109,18 @@
float getTransitionPoint() {
return mHbmController.getTransitionPoint();
}
+
+ private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
+ if (NBM_FEATURE_FLAG) {
+ boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
+ hbmChangesFunc.run();
+ // if nbm transition changed - trigger callback
+ // HighBrightnessModeController handles sending changes itself
+ if (nbmTransitionChanged) {
+ mModeChangeCallback.run();
+ }
+ } else {
+ hbmChangesFunc.run();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7a797dd..7ccfb44 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -41,6 +41,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BlockingZoneConfig;
+import com.android.server.display.config.BrightnessLimitMap;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
@@ -51,8 +52,11 @@
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.IntegerArray;
+import com.android.server.display.config.LuxThrottling;
import com.android.server.display.config.NitsMap;
+import com.android.server.display.config.NonNegativeFloatToFloatPoint;
import com.android.server.display.config.Point;
+import com.android.server.display.config.PredefinedBrightnessLimitNames;
import com.android.server.display.config.RefreshRateConfigs;
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.RefreshRateThrottlingMap;
@@ -219,6 +223,22 @@
* <allowInLowPowerMode>false</allowInLowPowerMode>
* </highBrightnessMode>
*
+ * <luxThrottling>
+ * <brightnessLimitMap>
+ * <type>default</type>
+ * <map>
+ * <point>
+ * <first>5000</first>
+ * <second>0.3</second>
+ * </point>
+ * <point>
+ * <first>5000</first>
+ * <second>0.3</second>
+ * </point>
+ * </map>
+ * </brightnessPeakMap>
+ * </luxThrottling>
+ *
* <quirks>
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
@@ -693,6 +713,9 @@
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
mRefreshRateThrottlingMap = new HashMap<>();
+ private final Map<BrightnessLimitMapType, Map<Float, Float>>
+ mLuxThrottlingData = new HashMap<>();
+
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -1344,6 +1367,11 @@
return hbmData;
}
+ @NonNull
+ public Map<BrightnessLimitMapType, Map<Float, Float>> getLuxThrottlingData() {
+ return mLuxThrottlingData;
+ }
+
public List<RefreshRateLimitation> getRefreshRateLimitations() {
return mRefreshRateLimitations;
}
@@ -1530,6 +1558,7 @@
+ ", mBrightnessDefault=" + mBrightnessDefault
+ ", mQuirks=" + mQuirks
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ + ", mLuxThrottlingData=" + mLuxThrottlingData
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mThermalBrightnessThrottlingDataMapByThrottlingId="
@@ -1676,6 +1705,7 @@
loadBrightnessMap(config);
loadThermalThrottlingConfig(config);
loadHighBrightnessModeData(config);
+ loadLuxThrottling(config);
loadQuirks(config);
loadBrightnessRamps(config);
loadAmbientLightSensorFromDdc(config);
@@ -2428,6 +2458,54 @@
}
}
+ private void loadLuxThrottling(DisplayConfiguration config) {
+ LuxThrottling cfg = config.getLuxThrottling();
+ if (cfg != null) {
+ HighBrightnessMode hbm = config.getHighBrightnessMode();
+ float hbmTransitionPoint = hbm != null ? hbm.getTransitionPoint_all().floatValue()
+ : PowerManager.BRIGHTNESS_MAX;
+ List<BrightnessLimitMap> limitMaps = cfg.getBrightnessLimitMap();
+ for (BrightnessLimitMap map : limitMaps) {
+ PredefinedBrightnessLimitNames type = map.getType();
+ BrightnessLimitMapType mappedType = BrightnessLimitMapType.convert(type);
+ if (mappedType == null) {
+ Slog.wtf(TAG, "Invalid NBM config: unsupported map type=" + type);
+ continue;
+ }
+ if (mLuxThrottlingData.containsKey(mappedType)) {
+ Slog.wtf(TAG, "Invalid NBM config: duplicate map type=" + mappedType);
+ continue;
+ }
+ Map<Float, Float> luxToTransitionPointMap = new HashMap<>();
+
+ List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
+ for (NonNegativeFloatToFloatPoint point : points) {
+ float lux = point.getFirst().floatValue();
+ float maxBrightness = point.getSecond().floatValue();
+ if (maxBrightness > hbmTransitionPoint) {
+ Slog.wtf(TAG,
+ "Invalid NBM config: maxBrightness is greater than hbm"
+ + ".transitionPoint. type="
+ + type + "; lux=" + lux + "; maxBrightness="
+ + maxBrightness);
+ continue;
+ }
+ if (luxToTransitionPointMap.containsKey(lux)) {
+ Slog.wtf(TAG,
+ "Invalid NBM config: duplicate lux key. type=" + type + "; lux="
+ + lux);
+ continue;
+ }
+ luxToTransitionPointMap.put(lux,
+ mBacklightToBrightnessSpline.interpolate(maxBrightness));
+ }
+ if (!luxToTransitionPointMap.isEmpty()) {
+ mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
+ }
+ }
+ }
+ }
+
private void loadBrightnessRamps(DisplayConfiguration config) {
// Priority 1: Value in the display device config (float)
// Priority 2: Value in the config.xml (int)
@@ -3155,4 +3233,19 @@
}
}
}
+
+ public enum BrightnessLimitMapType {
+ DEFAULT, ADAPTIVE;
+
+ @Nullable
+ private static BrightnessLimitMapType convert(PredefinedBrightnessLimitNames type) {
+ switch (type) {
+ case _default:
+ return DEFAULT;
+ case adaptive:
+ return ADAPTIVE;
+ }
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7d8bde9..80114cc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2141,7 +2141,9 @@
}
void postBrightnessChangeRunnable() {
- mHandler.post(mOnBrightnessChangeRunnable);
+ if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
}
private HighBrightnessModeController createHbmControllerLocked(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 040cecc..c8b0a72 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1788,7 +1788,9 @@
}
void postBrightnessChangeRunnable() {
- mHandler.post(mOnBrightnessChangeRunnable);
+ if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
}
private HighBrightnessModeController createHbmControllerLocked(
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
index 91e4a9e..dbabc24 100644
--- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -16,36 +16,79 @@
package com.android.server.display;
+import android.annotation.NonNull;
import android.os.PowerManager;
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
import java.util.HashMap;
import java.util.Map;
+/**
+ * Limits brightness for normal-brightness mode, based on ambient lux
+ **/
class NormalBrightnessModeController {
- private Map<Float, Float> mTransitionPoints = new HashMap<>();
+ @NonNull
+ private Map<BrightnessLimitMapType, Map<Float, Float>> mMaxBrightnessLimits = new HashMap<>();
+ private float mAmbientLux = Float.MAX_VALUE;
+ private boolean mAutoBrightnessEnabled = false;
// brightness limit in normal brightness mode, based on ambient lux.
- private float mVirtualTransitionPoint = PowerManager.BRIGHTNESS_MAX;
+ private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
boolean onAmbientLuxChange(float ambientLux) {
- float currentAmbientBoundary = Float.MAX_VALUE;
- float currentTransitionPoint = PowerManager.BRIGHTNESS_MAX;
- for (Map.Entry<Float, Float> transitionPoint: mTransitionPoints.entrySet()) {
- float ambientBoundary = transitionPoint.getKey();
- // find ambient lux upper boundary closest to current ambient lux
- if (ambientBoundary > ambientLux && ambientBoundary < currentAmbientBoundary) {
- currentTransitionPoint = transitionPoint.getValue();
- currentAmbientBoundary = ambientBoundary;
- }
- }
- if (mVirtualTransitionPoint != currentTransitionPoint) {
- mVirtualTransitionPoint = currentTransitionPoint;
- return true;
+ mAmbientLux = ambientLux;
+ return recalculateMaxBrightness();
+ }
+
+ boolean setAutoBrightnessState(int state) {
+ boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ if (isEnabled != mAutoBrightnessEnabled) {
+ mAutoBrightnessEnabled = isEnabled;
+ return recalculateMaxBrightness();
}
return false;
}
float getCurrentBrightnessMax() {
- return mVirtualTransitionPoint;
+ return mMaxBrightness;
+ }
+
+ boolean resetNbmData(
+ @NonNull Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessLimits) {
+ mMaxBrightnessLimits = maxBrightnessLimits;
+ return recalculateMaxBrightness();
+ }
+
+ private boolean recalculateMaxBrightness() {
+ float foundAmbientBoundary = Float.MAX_VALUE;
+ float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ Map<Float, Float> maxBrightnessPoints = null;
+
+ if (mAutoBrightnessEnabled) {
+ maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE);
+ }
+
+ if (maxBrightnessPoints == null) {
+ maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT);
+ }
+
+ if (maxBrightnessPoints != null) {
+ for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) {
+ float ambientBoundary = brightnessPoint.getKey();
+ // find ambient lux upper boundary closest to current ambient lux
+ if (ambientBoundary > mAmbientLux && ambientBoundary < foundAmbientBoundary) {
+ foundMaxBrightness = brightnessPoint.getValue();
+ foundAmbientBoundary = ambientBoundary;
+ }
+ }
+ }
+
+ if (mMaxBrightness != foundMaxBrightness) {
+ mMaxBrightness = foundMaxBrightness;
+ return true;
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 44f6900..7bb0489 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -31,7 +31,6 @@
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
@@ -40,8 +39,12 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
@@ -278,8 +281,6 @@
protected IGateKeeperService mGateKeeperService;
protected IAuthSecret mAuthSecretService;
- private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
-
/**
* The UIDs that are used for system credential storage in keystore.
*/
@@ -311,6 +312,7 @@
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mLockSettingsService.migrateOldDataAfterSystemReady();
mLockSettingsService.loadEscrowData();
+ mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
}
}
@@ -544,7 +546,7 @@
}
public boolean isGsiRunning() {
- return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+ return LockPatternUtils.isGsiRunning();
}
public FingerprintManager getFingerprintManager() {
@@ -952,6 +954,16 @@
return success;
}
+ @VisibleForTesting
+ void deleteRepairModePersistentDataIfNeeded() {
+ if (!LockPatternUtils.isRepairModeSupported(mContext)
+ || LockPatternUtils.isRepairModeActive(mContext)
+ || mInjector.isGsiRunning()) {
+ return;
+ }
+ mStorage.deleteRepairModePersistentData();
+ }
+
// This is called when Weaver is guaranteed to be available (if the device supports Weaver).
// It does any synthetic password related work that was delayed from earlier in the boot.
private void onThirdPartyAppsStarted() {
@@ -1282,8 +1294,8 @@
* {@link #CREDENTIAL_TYPE_PASSWORD}
*/
private int getCredentialTypeInternal(int userId) {
- if (userId == USER_FRP) {
- return getFrpCredentialType();
+ if (isSpecialUserId(userId)) {
+ return mSpManager.getSpecialUserCredentialType(userId);
}
synchronized (mSpManager) {
final long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -1299,29 +1311,6 @@
}
}
- private int getFrpCredentialType() {
- PersistentData data = mStorage.readPersistentDataBlock();
- if (data.type != PersistentData.TYPE_SP_GATEKEEPER &&
- data.type != PersistentData.TYPE_SP_WEAVER) {
- return CREDENTIAL_TYPE_NONE;
- }
- int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
- if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
- return credentialType;
- }
- return pinOrPasswordQualityToCredentialType(data.qualityForUi);
- }
-
- private static int pinOrPasswordQualityToCredentialType(int quality) {
- if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
- return CREDENTIAL_TYPE_PASSWORD;
- }
- if (LockPatternUtils.isQualityNumericPin(quality)) {
- return CREDENTIAL_TYPE_PIN;
- }
- throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
- }
-
private boolean isUserSecure(int userId) {
return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
}
@@ -1563,8 +1552,8 @@
* unlock operation.
*/
private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId) {
- // Don't send credentials during the factory reset protection flow.
- if (userId == USER_FRP) {
+ // Don't send credentials during the special user flow.
+ if (isSpecialUserId(userId)) {
return;
}
@@ -2184,15 +2173,19 @@
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
}
+ if (userId == USER_REPAIR_MODE && !LockPatternUtils.isRepairModeActive(mContext)) {
+ Slog.e(TAG, "Repair mode is not active on the device.");
+ return VerifyCredentialResponse.ERROR;
+ }
Slogf.i(TAG, "Verifying lockscreen credential for user %d", userId);
final AuthenticationResult authResult;
VerifyCredentialResponse response;
synchronized (mSpManager) {
- if (userId == USER_FRP) {
- return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
- progressCallback);
+ if (isSpecialUserId(userId)) {
+ return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
+ credential, progressCallback);
}
long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -2201,6 +2194,12 @@
response = authResult.gkResponse;
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
+ if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
+ Slog.e(TAG, "Failed to write repair mode credential");
+ return VerifyCredentialResponse.ERROR;
+ }
+ }
// credential has matched
mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
authResult.syntheticPassword.deriveGkPassword());
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 2fa637e..1c5ecb7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -19,7 +19,7 @@
import static android.content.Context.USER_SERVICE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
@@ -90,6 +90,9 @@
private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
+ private static final String REPAIR_MODE_DIRECTORY = "repair-mode/";
+ private static final String REPAIR_MODE_PERSISTENT_FILE = "pst";
+
private static final Object DEFAULT = new Object();
private static final String[] SETTINGS_TO_BACKUP = new String[] {
@@ -390,6 +393,29 @@
}
}
+ @VisibleForTesting
+ File getRepairModePersistentDataFile() {
+ final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY);
+ return new File(directory, REPAIR_MODE_PERSISTENT_FILE);
+ }
+
+ public PersistentData readRepairModePersistentData() {
+ final byte[] data = readFile(getRepairModePersistentDataFile());
+ if (data == null) {
+ return PersistentData.NONE;
+ }
+ return PersistentData.fromBytes(data);
+ }
+
+ public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) {
+ writeFile(getRepairModePersistentDataFile(),
+ PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload));
+ }
+
+ public void deleteRepairModePersistentData() {
+ deleteFile(getRepairModePersistentDataFile());
+ }
+
/**
* Writes the synthetic password state file for the given user ID, protector ID, and state name.
* If the file already exists, then it is atomically replaced.
@@ -510,7 +536,8 @@
}
public void setString(String key, String value, int userId) {
- Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
+ Preconditions.checkArgument(!isSpecialUserId(userId),
+ "cannot store lock settings for special user: %d", userId);
writeKeyValue(key, value, userId);
if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
@@ -535,7 +562,7 @@
}
public String getString(String key, String defaultValue, int userId) {
- if (userId == USER_FRP) {
+ if (isSpecialUserId(userId)) {
return null;
}
return readKeyValue(key, defaultValue, userId);
@@ -583,6 +610,17 @@
}
}
+ /**
+ * Provides a concrete data structure to represent the minimal information from
+ * a user's LSKF-based SP protector that is needed to verify the user's LSKF,
+ * in combination with the corresponding Gatekeeper enrollment or Weaver slot.
+ * It can be stored in {@link com.android.server.PersistentDataBlockService} for
+ * FRP to live across factory resets not initiated via the Settings UI.
+ * Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for
+ * exiting repair mode, since the device runs with an empty data partition in
+ * repair mode and the same credential be provided to exit repair mode is
+ * required.
+ */
public static class PersistentData {
static final byte VERSION_1 = 1;
static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
@@ -685,6 +723,19 @@
}
pw.decreaseIndent();
}
+ // Dump repair mode file states
+ final File repairModeFile = getRepairModePersistentDataFile();
+ if (repairModeFile.exists()) {
+ pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent()));
+ pw.increaseIndent();
+ pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(),
+ LockSettingsService.timestampToString(repairModeFile.lastModified()),
+ repairModeFile.getName()));
+ final PersistentData data = readRepairModePersistentData();
+ pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d",
+ data.type, data.userId, data.payload != null ? data.payload.length : 0));
+ pw.decreaseIndent();
+ }
}
static class DatabaseHelper extends SQLiteOpenHelper {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index e8fd6f8..d359a5f 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -16,9 +16,14 @@
package com.android.server.locksettings;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -718,11 +723,30 @@
return PasswordData.fromBytes(passwordData).credentialType;
}
- static int getFrpCredentialType(byte[] payload) {
- if (payload == null) {
+ int getSpecialUserCredentialType(int userId) {
+ final PersistentData data = getSpecialUserPersistentData(userId);
+ if (data.type != PersistentData.TYPE_SP_GATEKEEPER
+ && data.type != PersistentData.TYPE_SP_WEAVER) {
+ return CREDENTIAL_TYPE_NONE;
+ }
+ if (data.payload == null) {
return LockPatternUtils.CREDENTIAL_TYPE_NONE;
}
- return PasswordData.fromBytes(payload).credentialType;
+ final int credentialType = PasswordData.fromBytes(data.payload).credentialType;
+ if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+ return credentialType;
+ }
+ return pinOrPasswordQualityToCredentialType(data.qualityForUi);
+ }
+
+ private PersistentData getSpecialUserPersistentData(int userId) {
+ if (userId == USER_FRP) {
+ return mStorage.readPersistentDataBlock();
+ }
+ if (userId == USER_REPAIR_MODE) {
+ return mStorage.readRepairModePersistentData();
+ }
+ throw new IllegalArgumentException("Unknown special user id " + userId);
}
/**
@@ -1005,10 +1029,10 @@
return sizeOfCredential;
}
- public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
- LockscreenCredential userCredential,
+ public VerifyCredentialResponse verifySpecialUserCredential(int sourceUserId,
+ IGateKeeperService gatekeeper, LockscreenCredential userCredential,
ICheckCredentialProgressCallback progressCallback) {
- PersistentData persistentData = mStorage.readPersistentDataBlock();
+ final PersistentData persistentData = getSpecialUserPersistentData(sourceUserId);
if (persistentData.type == PersistentData.TYPE_SP_GATEKEEPER) {
PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
byte[] stretchedLskf = stretchLskf(userCredential, pwd);
@@ -1019,13 +1043,13 @@
0 /* challenge */, pwd.passwordHandle,
stretchedLskfToGkPassword(stretchedLskf));
} catch (RemoteException e) {
- Slog.e(TAG, "FRP verifyChallenge failed", e);
+ Slog.e(TAG, "Persistent data credential verifyChallenge failed", e);
return VerifyCredentialResponse.ERROR;
}
return VerifyCredentialResponse.fromGateKeeperResponse(response);
} else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
if (!isWeaverAvailable()) {
- Slog.e(TAG, "No weaver service to verify SP-based FRP credential");
+ Slog.e(TAG, "No weaver service to verify SP-based persistent data credential");
return VerifyCredentialResponse.ERROR;
}
PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
@@ -1114,6 +1138,57 @@
}
}
+ /**
+ * Writes the user's synthetic password data to the repair mode file.
+ *
+ * @param protectorId current LSKF based protectorId
+ * @param userId user id of the user
+ */
+ public boolean writeRepairModeCredentialLocked(long protectorId, int userId) {
+ if (!shouldWriteRepairModeCredential(userId)) {
+ return false;
+ }
+ final byte[] data = loadState(PASSWORD_DATA_NAME, protectorId, userId);
+ if (data == null) {
+ Slogf.w(TAG, "Password data not found for user %d", userId);
+ return false;
+ }
+ final PasswordData pwd = PasswordData.fromBytes(data);
+ if (isNoneCredential(pwd)) {
+ Slogf.w(TAG, "User %d has NONE credential", userId);
+ return false;
+ }
+ Slogf.d(TAG, "Writing repair mode credential tied to user %d", userId);
+ final int weaverSlot = loadWeaverSlot(protectorId, userId);
+ if (weaverSlot != INVALID_WEAVER_SLOT) {
+ // write weaver password
+ mStorage.writeRepairModePersistentData(
+ PersistentData.TYPE_SP_WEAVER, weaverSlot, pwd.toBytes());
+ } else {
+ // write gatekeeper password
+ mStorage.writeRepairModePersistentData(
+ PersistentData.TYPE_SP_GATEKEEPER, userId, pwd.toBytes());
+ }
+ return true;
+ }
+
+ private boolean shouldWriteRepairModeCredential(int userId) {
+ final UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (!LockPatternUtils.canUserEnterRepairMode(mContext, userInfo)) {
+ Slogf.w(TAG, "User %d can't enter repair mode", userId);
+ return false;
+ }
+ if (LockPatternUtils.isRepairModeActive(mContext)) {
+ Slog.w(TAG, "Can't write repair mode credential while repair mode is already active");
+ return false;
+ }
+ if (LockPatternUtils.isGsiRunning()) {
+ Slog.w(TAG, "Can't write repair mode credential while GSI is running");
+ return false;
+ }
+ return true;
+ }
+
private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
/**
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index e5ffa7e..39b8bfd 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -34,8 +34,6 @@
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
-import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
@@ -43,15 +41,12 @@
import android.net.INetd;
import android.net.INetdUnsolicitedEventListener;
import android.net.INetworkManagementEventObserver;
-import android.net.ITetheringStatsProvider;
import android.net.InetAddresses;
import android.net.InterfaceConfiguration;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.NetworkPolicyManager;
-import android.net.NetworkStack;
-import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.util.NetdService;
import android.os.BatteryStats;
@@ -78,28 +73,20 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetdUtils.ModifyOperation;
+import com.android.net.module.util.PermissionUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.google.android.collect.Maps;
-import java.io.BufferedReader;
-import java.io.DataInputStream;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
-import java.net.InterfaceAddress;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
-import java.util.Objects;
/**
* @hide
@@ -129,13 +116,6 @@
private static final String TAG = "NetworkManagement";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int MAX_UID_RANGES_PER_COMMAND = 10;
-
- static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
-
- static final boolean MODIFY_OPERATION_ADD = true;
- static final boolean MODIFY_OPERATION_REMOVE = false;
-
/**
* Binder context for this service
*/
@@ -154,10 +134,6 @@
private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
new RemoteCallbackList<>();
- @GuardedBy("mTetheringStatsProviders")
- private final HashMap<ITetheringStatsProvider, String>
- mTetheringStatsProviders = Maps.newHashMap();
-
/**
* If both locks need to be held, then they should be obtained in the order:
* first {@link #mQuotaLock} and then {@link #mRulesLock}.
@@ -182,35 +158,35 @@
private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
/** Set of UIDs that are to be blocked/allowed by firewall controller. */
@GuardedBy("mRulesLock")
- private SparseIntArray mUidFirewallRules = new SparseIntArray();
+ private final SparseIntArray mUidFirewallRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to application idles.
*/
@GuardedBy("mRulesLock")
- private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
+ private final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to device idles.
*/
@GuardedBy("mRulesLock")
- private SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
+ private final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to device on power-save mode.
*/
@GuardedBy("mRulesLock")
- private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
+ private final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
/**
* Contains the per-UID firewall rules that are used when Restricted Networking Mode is enabled.
*/
@GuardedBy("mRulesLock")
- private SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray();
+ private final SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray();
/**
* Contains the per-UID firewall rules that are used when Low Power Standby is enabled.
*/
@GuardedBy("mRulesLock")
- private SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+ private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -237,17 +213,6 @@
mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
mDeps.registerLocalService(new LocalService());
-
- synchronized (mTetheringStatsProviders) {
- mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd");
- }
- }
-
- private NetworkManagementService() {
- mContext = null;
- mDaemonHandler = null;
- mDeps = null;
- mNetdUnsolicitedEventListener = null;
}
static NetworkManagementService create(Context context, Dependencies deps)
@@ -290,19 +255,19 @@
@Override
public void registerObserver(INetworkManagementEventObserver observer) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
mObservers.register(observer);
}
@Override
public void unregisterObserver(INetworkManagementEventObserver observer) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
mObservers.unregister(observer);
}
@FunctionalInterface
private interface NetworkManagementEventCallback {
- public void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
+ void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
}
private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
@@ -368,35 +333,6 @@
type, isActive, tsNanos, uid));
}
- @Override
- public void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name) {
- NetworkStack.checkNetworkStackPermission(mContext);
- Objects.requireNonNull(provider);
- synchronized(mTetheringStatsProviders) {
- mTetheringStatsProviders.put(provider, name);
- }
- }
-
- @Override
- public void unregisterTetheringStatsProvider(ITetheringStatsProvider provider) {
- NetworkStack.checkNetworkStackPermission(mContext);
- synchronized(mTetheringStatsProviders) {
- mTetheringStatsProviders.remove(provider);
- }
- }
-
- @Override
- public void tetherLimitReached(ITetheringStatsProvider provider) {
- NetworkStack.checkNetworkStackPermission(mContext);
- synchronized(mTetheringStatsProviders) {
- if (!mTetheringStatsProviders.containsKey(provider)) {
- return;
- }
- // No current code examines the interface parameter in a global alert. Just pass null.
- mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
- }
- }
-
// Sync the state of the given chain with the native daemon.
private void syncFirewallChainLocked(int chain, String name) {
SparseIntArray rules;
@@ -666,7 +602,7 @@
public String[] listInterfaces() {
// TODO: Remove CONNECTIVITY_INTERNAL after bluetooth tethering has no longer called these
// APIs.
- NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
try {
return mNetdService.interfaceGetList();
} catch (RemoteException | ServiceSpecificException e) {
@@ -718,7 +654,7 @@
public InterfaceConfiguration getInterfaceConfig(String iface) {
// TODO: Remove CONNECTIVITY_INTERNAL after bluetooth tethering has no longer called these
// APIs.
- NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
final InterfaceConfigurationParcel result;
try {
result = mNetdService.interfaceGetCfg(iface);
@@ -738,7 +674,7 @@
public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
// TODO: Remove CONNECTIVITY_INTERNAL after bluetooth tethering has no longer called these
// APIs.
- NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
LinkAddress linkAddr = cfg.getLinkAddress();
if (linkAddr == null || linkAddr.getAddress() == null) {
throw new IllegalStateException("Null LinkAddress given");
@@ -755,7 +691,7 @@
@Override
public void setInterfaceDown(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
ifcg.setInterfaceDown();
setInterfaceConfig(iface, ifcg);
@@ -763,7 +699,7 @@
@Override
public void setInterfaceUp(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
ifcg.setInterfaceUp();
setInterfaceConfig(iface, ifcg);
@@ -771,7 +707,7 @@
@Override
public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable);
} catch (RemoteException | ServiceSpecificException e) {
@@ -783,7 +719,7 @@
IPv6 addresses on interface down, but we need to do full clean up here */
@Override
public void clearInterfaceAddresses(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.interfaceClearAddrs(iface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -793,7 +729,7 @@
@Override
public void enableIpv6(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.interfaceSetEnableIPv6(iface, true);
} catch (RemoteException | ServiceSpecificException e) {
@@ -803,7 +739,7 @@
@Override
public void setIPv6AddrGenMode(String iface, int mode) throws ServiceSpecificException {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.setIPv6AddrGenMode(iface, mode);
} catch (RemoteException e) {
@@ -813,7 +749,7 @@
@Override
public void disableIpv6(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.interfaceSetEnableIPv6(iface, false);
} catch (RemoteException | ServiceSpecificException e) {
@@ -823,44 +759,16 @@
@Override
public void addRoute(int netId, RouteInfo route) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
NetdUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route);
}
@Override
public void removeRoute(int netId, RouteInfo route) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
NetdUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route);
}
- private ArrayList<String> readRouteList(String filename) {
- FileInputStream fstream = null;
- ArrayList<String> list = new ArrayList<>();
-
- try {
- fstream = new FileInputStream(filename);
- DataInputStream in = new DataInputStream(fstream);
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
- String s;
-
- // throw away the title line
-
- while (((s = br.readLine()) != null) && (s.length() != 0)) {
- list.add(s);
- }
- } catch (IOException ex) {
- // return current list, possibly empty
- } finally {
- if (fstream != null) {
- try {
- fstream.close();
- } catch (IOException ex) {}
- }
- }
-
- return list;
- }
-
@android.annotation.EnforcePermission(android.Manifest.permission.SHUTDOWN)
@Override
public void shutdown() {
@@ -873,11 +781,10 @@
@Override
public boolean getIpForwardingEnabled() throws IllegalStateException{
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
- final boolean isEnabled = mNetdService.ipfwdEnabled();
- return isEnabled;
+ return mNetdService.ipfwdEnabled();
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
@@ -885,7 +792,7 @@
@Override
public void setIpForwardingEnabled(boolean enable) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
if (enable) {
mNetdService.ipfwdEnableForwarding("tethering");
@@ -899,14 +806,9 @@
@Override
public void startTethering(String[] dhcpRange) {
- startTetheringWithConfiguration(true, dhcpRange);
- }
-
- @Override
- public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
- NetdUtils.tetherStart(mNetdService, usingLegacyDnsProxy, dhcpRange);
+ NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
@@ -914,7 +816,7 @@
@Override
public void stopTethering() {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.tetherStop();
} catch (RemoteException | ServiceSpecificException e) {
@@ -924,11 +826,9 @@
@Override
public boolean isTetheringStarted() {
- NetworkStack.checkNetworkStackPermission(mContext);
-
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
- final boolean isEnabled = mNetdService.tetherIsEnabled();
- return isEnabled;
+ return mNetdService.tetherIsEnabled();
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
@@ -936,7 +836,7 @@
@Override
public void tetherInterface(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
@@ -948,7 +848,7 @@
@Override
public void untetherInterface(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
NetdUtils.untetherInterface(mNetdService, iface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -958,7 +858,7 @@
@Override
public String[] listTetheredInterfaces() {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
return mNetdService.tetherInterfaceList();
} catch (RemoteException | ServiceSpecificException e) {
@@ -967,51 +867,8 @@
}
@Override
- public String[] getDnsForwarders() {
- NetworkStack.checkNetworkStackPermission(mContext);
- try {
- return mNetdService.tetherDnsList();
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private List<InterfaceAddress> excludeLinkLocal(List<InterfaceAddress> addresses) {
- ArrayList<InterfaceAddress> filtered = new ArrayList<>(addresses.size());
- for (InterfaceAddress ia : addresses) {
- if (!ia.getAddress().isLinkLocalAddress())
- filtered.add(ia);
- }
- return filtered;
- }
-
- private void modifyInterfaceForward(boolean add, String fromIface, String toIface) {
- try {
- if (add) {
- mNetdService.ipfwdAddInterfaceForward(fromIface, toIface);
- } else {
- mNetdService.ipfwdRemoveInterfaceForward(fromIface, toIface);
- }
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void startInterfaceForwarding(String fromIface, String toIface) {
- NetworkStack.checkNetworkStackPermission(mContext);
- modifyInterfaceForward(true, fromIface, toIface);
- }
-
- @Override
- public void stopInterfaceForwarding(String fromIface, String toIface) {
- NetworkStack.checkNetworkStackPermission(mContext);
- modifyInterfaceForward(false, fromIface, toIface);
- }
-
- @Override
public void enableNat(String internalInterface, String externalInterface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.tetherAddForward(internalInterface, externalInterface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -1021,7 +878,7 @@
@Override
public void disableNat(String internalInterface, String externalInterface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.tetherRemoveForward(internalInterface, externalInterface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -1031,7 +888,7 @@
@Override
public void setInterfaceQuota(String iface, long quotaBytes) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
synchronized (mQuotaLock) {
if (mActiveQuotas.containsKey(iface)) {
@@ -1046,23 +903,12 @@
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
-
- synchronized (mTetheringStatsProviders) {
- for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
- try {
- provider.setInterfaceQuota(iface, quotaBytes);
- } catch (RemoteException e) {
- Log.e(TAG, "Problem setting tethering data limit on provider " +
- mTetheringStatsProviders.get(provider) + ": " + e);
- }
- }
- }
}
}
@Override
public void removeInterfaceQuota(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
synchronized (mQuotaLock) {
if (!mActiveQuotas.containsKey(iface)) {
@@ -1079,23 +925,12 @@
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
-
- synchronized (mTetheringStatsProviders) {
- for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
- try {
- provider.setInterfaceQuota(iface, ITetheringStatsProvider.QUOTA_UNLIMITED);
- } catch (RemoteException e) {
- Log.e(TAG, "Problem removing tethering data limit on provider " +
- mTetheringStatsProviders.get(provider) + ": " + e);
- }
- }
- }
}
}
@Override
public void setInterfaceAlert(String iface, long alertBytes) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
// quick validity check
if (!mActiveQuotas.containsKey(iface)) {
@@ -1119,7 +954,7 @@
@Override
public void removeInterfaceAlert(String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
synchronized (mQuotaLock) {
if (!mActiveAlerts.containsKey(iface)) {
@@ -1137,19 +972,8 @@
}
}
- @Override
- public void setGlobalAlert(long alertBytes) {
- NetworkStack.checkNetworkStackPermission(mContext);
-
- try {
- mNetdService.bandwidthSetGlobalAlert(alertBytes);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
private void setUidOnMeteredNetworkList(int uid, boolean allowlist, boolean enable) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
synchronized (mQuotaLock) {
boolean oldEnable;
@@ -1261,7 +1085,7 @@
@Override
public void setUidCleartextNetworkPolicy(int uid, int policy) {
if (mDeps.getCallingUid() != uid) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
}
synchronized (mQuotaLock) {
@@ -1298,27 +1122,6 @@
return true;
}
- private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
- @Override
- public NetworkStats getTetherStats(int how) {
- // Remove the implementation of NetdTetheringStatsProvider#getTetherStats
- // since all callers are migrated to use INetd#tetherGetStats directly.
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setInterfaceQuota(String iface, long quotaBytes) {
- // Do nothing. netd is already informed of quota changes in setInterfaceQuota.
- }
- }
-
- @Override
- public NetworkStats getNetworkStatsTethering(int how) {
- // Remove the implementation of getNetworkStatsTethering since all callers are migrated
- // to use INetd#tetherGetStats directly.
- throw new UnsupportedOperationException();
- }
-
@Override
public void setFirewallEnabled(boolean enabled) {
enforceSystemUid();
@@ -1338,18 +1141,6 @@
}
@Override
- public void setFirewallInterfaceRule(String iface, boolean allow) {
- enforceSystemUid();
- Preconditions.checkState(mFirewallEnabled);
- try {
- mNetdService.firewallSetInterfaceRule(iface,
- allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
public void setFirewallChainEnabled(int chain, boolean enable) {
enforceSystemUid();
synchronized (mQuotaLock) {
@@ -1619,22 +1410,9 @@
pw.println("]");
}
- private void modifyInterfaceInNetwork(boolean add, int netId, String iface) {
- NetworkStack.checkNetworkStackPermission(mContext);
- try {
- if (add) {
- mNetdService.networkAddInterface(netId, iface);
- } else {
- mNetdService.networkRemoveInterface(netId, iface);
- }
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
@Override
public void allowProtect(int uid) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.networkSetProtectAllow(uid);
@@ -1645,7 +1423,7 @@
@Override
public void denyProtect(int uid) {
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
try {
mNetdService.networkSetProtectDeny(uid);
@@ -1654,24 +1432,6 @@
}
}
- @Override
- public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
- modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface);
- // modifyInterfaceInNetwork already check calling permission.
- NetdUtils.addRoutesToLocalNetwork(mNetdService, iface, routes);
- }
-
- @Override
- public void removeInterfaceFromLocalNetwork(String iface) {
- modifyInterfaceInNetwork(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, iface);
- }
-
- @Override
- public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) {
- NetworkStack.checkNetworkStackPermission(mContext);
- return NetdUtils.removeRoutesFromLocalNetwork(mNetdService, routes);
- }
-
@android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
@Override
public boolean isNetworkRestricted(int uid) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 878da54..468fa6f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6595,7 +6595,10 @@
}
private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
- if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) {
+ if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)
+ && Binder.withCleanCallingIdentity(
+ () -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, false))) {
// The package probably doesn't have WAKE_LOCK permission and should not require it.
return Binder.withCleanCallingIdentity(() -> {
WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 5f52c16..2e85c97 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -343,6 +343,20 @@
return res ? DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
+ /** Deletes dexopt artifacts for the given package*/
+ private void deleteArtDexoptArtifacts(String packageName) {
+ try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ try {
+ DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+ filteredSnapshot, packageName);
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ Slog.w(TAG, e.toString());
+ }
+ }
+ }
+
/*
* This method handles package deletion in general
*/
@@ -484,6 +498,11 @@
action, allUserHandles, writeSettings);
} else {
if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
+ if (ps.isIncremental()) {
+ // Explicitly delete dexopt artifacts for incremental app because the
+ // artifacts are not stored in the same directory as the APKs
+ deleteArtDexoptArtifacts(packageName);
+ }
deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
outInfo, writeSettings);
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index fa7c063..f0d5e14 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -692,7 +692,7 @@
if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS)
!= 0) {
permissionParamsBuilder.setAllowlistedRestrictedPermissions(
- pkgSetting.getPkg().getRequestedPermissions());
+ new ArrayList<>(pkgSetting.getPkg().getRequestedPermissions()));
}
mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
Process.INVALID_UID /* previousAppId */,
@@ -2314,9 +2314,7 @@
& PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0;
if (grantRequestedPermissions) {
var permissionStates = new ArrayMap<String, Integer>();
- var requestedPermissions = pkg.getRequestedPermissions();
- for (int index = 0; index < requestedPermissions.size(); index++) {
- var permissionName = requestedPermissions.get(index);
+ for (var permissionName : pkg.getRequestedPermissions()) {
permissionStates.put(permissionName,
PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED);
}
@@ -2332,7 +2330,8 @@
(installRequest.getInstallFlags()
& PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
final List<String> allowlistedRestrictedPermissions =
- allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions()
+ allowlistAllRestrictedPermissions
+ ? new ArrayList<>(pkg.getRequestedPermissions())
: installRequest.getAllowlistedRestrictedPermissions();
if (allowlistedRestrictedPermissions != null) {
permissionParamsBuilder.setAllowlistedRestrictedPermissions(
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2edf978..be03125 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5021,10 +5021,9 @@
if ((permissionNames != null || dumpAll) && pkg != null
&& pkg.getRequestedPermissions() != null
&& pkg.getRequestedPermissions().size() > 0) {
- final List<String> perms = pkg.getRequestedPermissions();
+ final Set<String> perms = pkg.getRequestedPermissions();
pw.print(prefix); pw.println(" requested permissions:");
- for (int i=0; i<perms.size(); i++) {
- String perm = perms.get(i);
+ for (String perm : perms) {
if (permissionNames != null
&& !permissionNames.contains(perm)) {
continue;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a020728..5b3514c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2547,10 +2547,7 @@
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"getShareTargets");
final ComponentName chooser = injectChooserActivity();
- final String pkg = (chooser != null
- && mPackageManagerInternal.getComponentEnabledSetting(chooser,
- injectBinderCallingUid(), userId) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
- ? chooser.getPackageName() : mContext.getPackageName();
+ final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName();
synchronized (mLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index f036835..9d7354e 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -160,8 +160,8 @@
*/
@NonNull
@Deprecated
- @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
- protected List<String> requestedPermissions = emptyList();
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+ protected Set<String> requestedPermissions = emptySet();
@NonNull
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
protected List<String> protectedBroadcasts = emptyList();
@@ -277,8 +277,8 @@
@NonNull
private List<ParsedUsesPermission> usesPermissions = emptyList();
@NonNull
- @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
- private List<String> implicitPermissions = emptyList();
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+ private Set<String> implicitPermissions = emptySet();
@NonNull
private Set<String> upgradeKeySets = emptySet();
@NonNull
@@ -967,7 +967,7 @@
@NonNull
@Override
- public List<String> getImplicitPermissions() {
+ public Set<String> getImplicitPermissions() {
return implicitPermissions;
}
@@ -1225,7 +1225,7 @@
@NonNull
@Override
@Deprecated
- public List<String> getRequestedPermissions() {
+ public Set<String> getRequestedPermissions() {
return requestedPermissions;
}
@@ -2729,7 +2729,7 @@
usesOptionalNativeLibraries = Collections.unmodifiableList(usesOptionalNativeLibraries);
originalPackages = Collections.unmodifiableList(originalPackages);
adoptPermissions = Collections.unmodifiableList(adoptPermissions);
- requestedPermissions = Collections.unmodifiableList(requestedPermissions);
+ requestedPermissions = Collections.unmodifiableSet(requestedPermissions);
protectedBroadcasts = Collections.unmodifiableList(protectedBroadcasts);
apexSystemServices = Collections.unmodifiableList(apexSystemServices);
@@ -2750,7 +2750,7 @@
featureGroups = Collections.unmodifiableList(featureGroups);
usesPermissions = Collections.unmodifiableList(usesPermissions);
usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
- implicitPermissions = Collections.unmodifiableList(implicitPermissions);
+ implicitPermissions = Collections.unmodifiableSet(implicitPermissions);
upgradeKeySets = Collections.unmodifiableSet(upgradeKeySets);
keySetMapping = Collections.unmodifiableMap(keySetMapping);
attributions = Collections.unmodifiableList(attributions);
@@ -3121,9 +3121,9 @@
dest.writeByteArray(this.restrictUpdateHash);
dest.writeStringList(this.originalPackages);
sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
- sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
+ sForInternedStringSet.parcel(this.requestedPermissions, dest, flags);
ParsingUtils.writeParcelableList(dest, this.usesPermissions);
- sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
+ sForInternedStringSet.parcel(this.implicitPermissions, dest, flags);
sForStringSet.parcel(this.upgradeKeySets, dest, flags);
ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
@@ -3273,10 +3273,10 @@
this.restrictUpdateHash = in.createByteArray();
this.originalPackages = in.createStringArrayList();
this.adoptPermissions = sForInternedStringList.unparcel(in);
- this.requestedPermissions = sForInternedStringList.unparcel(in);
+ this.requestedPermissions = sForInternedStringSet.unparcel(in);
this.usesPermissions = ParsingUtils.createTypedInterfaceList(in,
ParsedUsesPermissionImpl.CREATOR);
- this.implicitPermissions = sForInternedStringList.unparcel(in);
+ this.implicitPermissions = sForInternedStringSet.unparcel(in);
this.upgradeKeySets = sForStringSet.unparcel(in);
this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in);
this.protectedBroadcasts = sForInternedStringList.unparcel(in);
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 30afb0b..dbe2d54 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -156,10 +156,13 @@
LocalServices.addService(PermissionManagerServiceInternal.class, localService);
LocalServices.addService(PermissionManagerInternal.class, localService);
- mPermissionManagerServiceImpl = new PermissionManagerServiceImpl(context,
- availableFeatures);
- //mPermissionManagerServiceImpl = new PermissionManagerServiceLoggingDecorator(
- // LocalServices.getService(PermissionManagerServiceInterface.class));
+ if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+ mPermissionManagerServiceImpl = LocalServices.getService(
+ PermissionManagerServiceInterface.class);
+ } else {
+ mPermissionManagerServiceImpl = new PermissionManagerServiceImpl(context,
+ availableFeatures);
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index a3ab889..20abfe6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1119,9 +1119,7 @@
ArrayList<String> allowlistedPermissions = null;
- final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
- for (int i = 0; i < permissionCount; i++) {
- final String permissionName = pkg.getRequestedPermissions().get(i);
+ for (final String permissionName : pkg.getRequestedPermissions()) {
final int currentFlags =
uidState.getPermissionFlags(permissionName);
if ((currentFlags & queryFlags) != 0) {
@@ -1748,10 +1746,7 @@
| FLAG_PERMISSION_POLICY_FIXED;
final String packageName = pkg.getPackageName();
- final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
- for (int i = 0; i < permissionCount; i++) {
- final String permName = pkg.getRequestedPermissions().get(i);
-
+ for (final String permName : pkg.getRequestedPermissions()) {
final boolean isRuntimePermission;
synchronized (mLock) {
final Permission permission = mRegistry.getPermission(permName);
@@ -2047,11 +2042,9 @@
final int callingUid = Binder.getCallingUid();
for (int userId: getAllUserIds()) {
- int numRequestedPermissions = newPackage.getRequestedPermissions().size();
- for (int i = 0; i < numRequestedPermissions; i++) {
- PermissionInfo permInfo = getPermissionInfo(
- newPackage.getRequestedPermissions().get(i),
- 0, newPackage.getPackageName());
+ for (final String permName : newPackage.getRequestedPermissions()) {
+ PermissionInfo permInfo = getPermissionInfo(permName, 0,
+ newPackage.getPackageName());
if (permInfo == null) {
continue;
}
@@ -2383,10 +2376,8 @@
if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
}
- n = pkg.getRequestedPermissions().size();
r = null;
- for (int i = 0; i < n; i++) {
- final String permissionName = pkg.getRequestedPermissions().get(i);
+ for (final String permissionName : pkg.getRequestedPermissions()) {
final Permission permission = mRegistry.getPermission(permissionName);
if (permission != null && permission.isAppOp()) {
mRegistry.removeAppOpPermissionPackage(permissionName,
@@ -2504,11 +2495,8 @@
ArraySet<String> shouldGrantSignaturePermission = null;
ArraySet<String> shouldGrantInternalPermission = null;
ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
- final List<String> requestedPermissions = pkg.getRequestedPermissions();
- final int requestedPermissionsSize = requestedPermissions.size();
- for (int i = 0; i < requestedPermissionsSize; i++) {
- final String permissionName = pkg.getRequestedPermissions().get(i);
-
+ final Set<String> requestedPermissions = pkg.getRequestedPermissions();
+ for (final String permissionName : pkg.getRequestedPermissions()) {
final Permission permission;
synchronized (mLock) {
permission = mRegistry.getPermission(permissionName);
@@ -2639,9 +2627,7 @@
ArraySet<String> newImplicitPermissions = new ArraySet<>();
final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
- for (int i = 0; i < requestedPermissionsSize; i++) {
- final String permName = requestedPermissions.get(i);
-
+ for (final String permName : requestedPermissions) {
final Permission bp = mRegistry.getPermission(permName);
final boolean appSupportsRuntimePermissions =
pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
@@ -2834,7 +2820,9 @@
} else if (!permissionPolicyInitialized
|| (!hardRestricted || restrictionExempt)) {
if ((origPermState != null && origPermState.isGranted())) {
- uidState.grantPermission(bp);
+ if (!uidState.grantPermission(bp)) {
+ wasChanged = true;
+ }
}
}
if (mIsLeanback && NOTIFICATION_PERMISSIONS.contains(permName)) {
@@ -3635,12 +3623,9 @@
@UserIdInt int userId) {
ArraySet<String> oldGrantedRestrictedPermissions = null;
boolean updatePermissions = false;
- final int permissionCount = pkg.getRequestedPermissions().size();
final int myUid = Process.myUid();
- for (int j = 0; j < permissionCount; j++) {
- final String permissionName = pkg.getRequestedPermissions().get(j);
-
+ for (final String permissionName : pkg.getRequestedPermissions()) {
final boolean isGranted;
synchronized (mLock) {
final Permission bp = mRegistry.getPermission(permissionName);
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e54f34d..91854fd 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -775,7 +775,7 @@
* @hide
*/
@NonNull
- List<String> getImplicitPermissions();
+ Set<String> getImplicitPermissions();
/**
* @see ApplicationInfo#installLocation
@@ -1123,7 +1123,7 @@
* @hide
*/
@NonNull
- List<String> getRequestedPermissions();
+ Set<String> getRequestedPermissions();
/**
* Whether or not the app requested explicitly resizeable Activities. Null value means nothing
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 7fc3356..f1f0fa3 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -456,7 +456,7 @@
List<ParsedActivity> getReceivers();
@NonNull
- List<String> getRequestedPermissions();
+ Set<String> getRequestedPermissions();
@Nullable
Boolean getResizeableActivity();
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 1567af0..e2cb87e 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2958,7 +2958,7 @@
final int listSize = mSplitPermissionInfos.size();
for (int is = 0; is < listSize; is++) {
final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
- List<String> requestedPermissions = pkg.getRequestedPermissions();
+ Set<String> requestedPermissions = pkg.getRequestedPermissions();
if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
|| !requestedPermissions.contains(spi.getSplitPermission())) {
continue;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 7cbc297..2679fce 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -583,6 +583,10 @@
}
private void grantOrUpgradeDefaultRuntimePermissionsIfNeeded(@UserIdInt int userId) {
+ if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+ return;
+ }
+
if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPermsIfNeeded(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f86d68a..f4edaa9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -602,8 +602,8 @@
// What we do when the user double-taps on home
private int mDoubleTapOnHomeBehavior;
- // Whether to lock the device after the next app transition has finished.
- boolean mLockAfterAppTransitionFinished;
+ // Whether to lock the device after the next dreaming transition has finished.
+ private boolean mLockAfterDreamingTransitionFinished;
// Allowed theater mode wake actions
private boolean mAllowTheaterModeWakeFromKey;
@@ -1106,7 +1106,7 @@
synchronized (mLock) {
// If the setting to lock instantly on power button press is true, then set the flag to
// lock after the dream transition has finished.
- mLockAfterAppTransitionFinished =
+ mLockAfterDreamingTransitionFinished =
mLockPatternUtils.getPowerButtonInstantlyLocks(mCurrentUserId);
}
@@ -2298,20 +2298,22 @@
true /* notifyOccluded */);
synchronized (mLock) {
- mLockAfterAppTransitionFinished = false;
+ mLockAfterDreamingTransitionFinished = false;
}
}
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
synchronized (mLock) {
- if (!mLockAfterAppTransitionFinished) {
- return;
+ final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+ // check both isDreaming and mLockAfterDreamingTransitionFinished before lockNow
+ // so it won't relock after dreaming has stopped
+ if (dreamManagerInternal != null && dreamManagerInternal.isDreaming()
+ && mLockAfterDreamingTransitionFinished) {
+ lockNow(null);
}
- mLockAfterAppTransitionFinished = false;
+ mLockAfterDreamingTransitionFinished = false;
}
-
- lockNow(null);
}
});
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index f45bda7..6cc4258 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -57,7 +57,6 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -314,11 +313,10 @@
dataOutputStream.writeInt(packageManagerInternal.getApplicationEnabledState(
pkg.getPackageName(), userId));
- final List<String> requestedPermissions = pkg.getRequestedPermissions();
- final int requestedPermissionsSize = requestedPermissions.size();
- dataOutputStream.writeInt(requestedPermissionsSize);
- for (int i = 0; i < requestedPermissionsSize; i++) {
- dataOutputStream.writeUTF(requestedPermissions.get(i));
+ final Set<String> requestedPermissions = pkg.getRequestedPermissions();
+ dataOutputStream.writeInt(requestedPermissions.size());
+ for (String permissionName : requestedPermissions) {
+ dataOutputStream.writeUTF(permissionName);
}
final ArraySet<String> enabledComponents =
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 43f96e7..99064bc 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -589,6 +589,8 @@
@Override
public int onCommand(String cmd) {
switch(cmd != null ? cmd : "") {
+ case "inject-temperature":
+ return runInjectTemperature();
case "override-status":
return runOverrideStatus();
case "reset":
@@ -611,6 +613,95 @@
}
}
+
+ private int runInjectTemperature() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final PrintWriter pw = getOutPrintWriter();
+ int type;
+ String typeName = getNextArgRequired();
+ switch (typeName.toUpperCase()) {
+ case "UNKNOWN":
+ type = Temperature.TYPE_UNKNOWN;
+ break;
+ case "CPU":
+ type = Temperature.TYPE_CPU;
+ break;
+ case "GPU":
+ type = Temperature.TYPE_GPU;
+ break;
+ case "BATTERY":
+ type = Temperature.TYPE_BATTERY;
+ break;
+ case "SKIN":
+ type = Temperature.TYPE_SKIN;
+ break;
+ case "USB_PORT":
+ type = Temperature.TYPE_USB_PORT;
+ break;
+ case "POWER_AMPLIFIER":
+ type = Temperature.TYPE_POWER_AMPLIFIER;
+ break;
+ case "BCL_VOLTAGE":
+ type = Temperature.TYPE_BCL_VOLTAGE;
+ break;
+ case "BCL_CURRENT":
+ type = Temperature.TYPE_BCL_CURRENT;
+ break;
+ case "BCL_PERCENTAGE":
+ type = Temperature.TYPE_BCL_PERCENTAGE;
+ break;
+ case "NPU":
+ type = Temperature.TYPE_NPU;
+ break;
+ default:
+ pw.println("Invalid temperature type: " + typeName);
+ return -1;
+ }
+ int throttle;
+ String throttleName = getNextArgRequired();
+ switch (throttleName.toUpperCase()) {
+ case "NONE":
+ throttle = Temperature.THROTTLING_NONE;
+ break;
+ case "LIGHT":
+ throttle = Temperature.THROTTLING_LIGHT;
+ break;
+ case "MODERATE":
+ throttle = Temperature.THROTTLING_MODERATE;
+ break;
+ case "SEVERE":
+ throttle = Temperature.THROTTLING_SEVERE;
+ break;
+ case "CRITICAL":
+ throttle = Temperature.THROTTLING_CRITICAL;
+ break;
+ case "EMERGENCY":
+ throttle = Temperature.THROTTLING_EMERGENCY;
+ break;
+ case "SHUTDOWN":
+ throttle = Temperature.THROTTLING_SHUTDOWN;
+ break;
+ default:
+ pw.println("Invalid throttle status: " + throttleName);
+ return -1;
+ }
+ String name = getNextArgRequired();
+ float value = 28.0f;
+ try {
+ String valueStr = getNextArg();
+ if (valueStr != null) value = Float.parseFloat(valueStr);
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex.toString());
+ return -1;
+ }
+ onTemperatureChanged(new Temperature(value, type, name, throttle), true);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private int runOverrideStatus() {
final long token = Binder.clearCallingIdentity();
try {
@@ -643,6 +734,9 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
+ pw.println(" inject-temperature TYPE STATUS NAME [VALUE]");
+ pw.println(" injects a new temperature sample for the specified device.");
+ pw.println(" type and status strings follow the names in android.os.Temperature.");
pw.println(" override-status STATUS");
pw.println(" sets and locks the thermal status of the device to STATUS.");
pw.println(" status code is defined in android.os.Temperature.");
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index b4613a7..efb3622 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -171,7 +171,7 @@
true, /* disableAod */
true, /* disableLaunchBoost */
true, /* disableOptionalSensors */
- true, /* disableVibration */
+ false, /* disableVibration */
false, /* enableAdjustBrightness */
false, /* enableDataSaver */
true, /* enableFirewall */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 9ff6a0d..d87fca4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -133,16 +133,14 @@
*/
final Rect cropHint = new Rect(0, 0, 0, 0);
- WallpaperData(int userId, File wallpaperDir, String inputFileName, String cropFileName) {
- this.userId = userId;
- wallpaperFile = new File(wallpaperDir, inputFileName);
- cropFile = new File(wallpaperDir, cropFileName);
- }
-
WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
- this(userId, getWallpaperDir(userId),
- (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER,
- (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP);
+ this.userId = userId;
+ this.mWhich = wallpaperType;
+ File wallpaperDir = getWallpaperDir(userId);
+ String wallpaperFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER;
+ String cropFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
+ this.wallpaperFile = new File(wallpaperDir, wallpaperFileName);
+ this.cropFile = new File(wallpaperDir, cropFileName);
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8bd5b67..534072c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1685,7 +1685,9 @@
}
// When running transient transition, the transient launch target should keep on top.
// So disallow the transient hide activity to move itself to front, e.g. trampoline.
- if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+ if (!mAvoidMoveToFront && (mService.mHomeProcess == null
+ || mService.mHomeProcess.mUid != realCallingUid)
+ && r.mTransitionController.isTransientHide(targetTask)) {
mAvoidMoveToFront = true;
}
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
@@ -2967,7 +2969,9 @@
// should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
&& intentActivity.inMultiWindowMode()
- && intentActivity == mTargetRootTask.topRunningActivity();
+ && intentActivity == mTargetRootTask.topRunningActivity()
+ && !intentActivity.mTransitionController.isTransientHide(
+ mTargetRootTask);
// We only want to move to the front, if we aren't going to launch on a
// different root task. If we launch on a different root task, we will put the
// task on top there.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3c97672..738797b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1891,7 +1891,7 @@
// DestroyActivityItem may be called first.
final ActivityRecord top = task.getTopMostActivity();
if (top != null && top.finishing && !top.mAppStopped && top.lastVisibleTime > 0
- && !task.mKillProcessesOnDestroyed) {
+ && !task.mKillProcessesOnDestroyed && top.hasProcess()) {
task.mKillProcessesOnDestroyed = true;
mHandler.sendMessageDelayed(
mHandler.obtainMessage(KILL_TASK_PROCESSES_TIMEOUT_MSG, task),
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 7e78393..0115877 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -185,6 +185,8 @@
}
} else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS) {
action = Operation.ACTION_SEAMLESS;
+ } else if (mDisplayContent.mTransitionController.mNavigationBarAttachedToApp) {
+ return;
}
mTargetWindowTokens.put(w.mToken, new Operation(action));
return;
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 07f3bc6..0af9fe9 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -15,6 +15,7 @@
lihongyu@google.com
mariiasand@google.com
rgl@google.com
+yunfanc@google.com
per-file BackgroundActivityStartController.java = set noparent
per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com, rickywai@google.com
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 48ca694..813ed20 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2374,10 +2374,14 @@
final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */,
display.mTransitionController, mWmService.mSyncEngine);
final TransitionController.OnStartCollect sendSleepTransition = (deferred) -> {
- display.mTransitionController.requestStartTransition(transition,
- null /* trigger */, null /* remote */, null /* display */);
- // Force playing immediately so that unrelated ops can't be collected.
- transition.playNow();
+ if (deferred && !display.shouldSleep()) {
+ transition.abort();
+ } else {
+ display.mTransitionController.requestStartTransition(transition,
+ null /* trigger */, null /* remote */, null /* display */);
+ // Force playing immediately so that unrelated ops can't be collected.
+ transition.playNow();
+ }
};
if (!display.mTransitionController.isCollecting()) {
// Since this bypasses sync, submit directly ignoring whether sync-engine
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 878b33f..1007357 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -20,6 +20,8 @@
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
import android.annotation.NonNull;
import android.content.Context;
@@ -59,6 +61,12 @@
private static final int SWIPE_FROM_RIGHT = 3;
private static final int SWIPE_FROM_LEFT = 4;
+ private static final int TRACKPAD_SWIPE_NONE = 0;
+ private static final int TRACKPAD_SWIPE_FROM_TOP = 1;
+ private static final int TRACKPAD_SWIPE_FROM_BOTTOM = 2;
+ private static final int TRACKPAD_SWIPE_FROM_RIGHT = 3;
+ private static final int TRACKPAD_SWIPE_FROM_LEFT = 4;
+
private final Context mContext;
private final Handler mHandler;
private int mDisplayCutoutTouchableRegionSize;
@@ -207,6 +215,25 @@
break;
case MotionEvent.ACTION_MOVE:
if (mSwipeFireable) {
+ int trackpadSwipe = detectTrackpadThreeFingerSwipe(event);
+ mSwipeFireable = trackpadSwipe == TRACKPAD_SWIPE_NONE;
+ if (!mSwipeFireable) {
+ if (trackpadSwipe == TRACKPAD_SWIPE_FROM_TOP) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop from trackpad");
+ mCallbacks.onSwipeFromTop();
+ } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_BOTTOM) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom from trackpad");
+ mCallbacks.onSwipeFromBottom();
+ } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_RIGHT) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight from trackpad");
+ mCallbacks.onSwipeFromRight();
+ } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_LEFT) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft from trackpad");
+ mCallbacks.onSwipeFromLeft();
+ }
+ break;
+ }
+
final int swipe = detectSwipe(event);
mSwipeFireable = swipe == SWIPE_NONE;
if (swipe == SWIPE_FROM_TOP) {
@@ -300,6 +327,31 @@
return mDownPointers - 1;
}
+ private int detectTrackpadThreeFingerSwipe(MotionEvent move) {
+ if (!isTrackpadThreeFingerSwipe(move)) {
+ return TRACKPAD_SWIPE_NONE;
+ }
+
+ float dx = move.getX() - mDownX[0];
+ float dy = move.getY() - mDownY[0];
+ if (Math.abs(dx) < Math.abs(dy)) {
+ if (Math.abs(dy) > mSwipeDistanceThreshold) {
+ return dy > 0 ? TRACKPAD_SWIPE_FROM_TOP : TRACKPAD_SWIPE_FROM_BOTTOM;
+ }
+ } else {
+ if (Math.abs(dx) > mSwipeDistanceThreshold) {
+ return dx > 0 ? TRACKPAD_SWIPE_FROM_LEFT : TRACKPAD_SWIPE_FROM_RIGHT;
+ }
+ }
+
+ return TRACKPAD_SWIPE_NONE;
+ }
+
+ private static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
+ }
+
private int detectSwipe(MotionEvent move) {
final int historySize = move.getHistorySize();
final int pointerCount = move.getPointerCount();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index bfdf84e..39772dda 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3451,6 +3451,7 @@
: INVALID_TASK_ID;
info.isFocused = isFocused();
info.isVisible = hasVisibleChildren();
+ info.isVisibleRequested = isVisibleRequested();
info.isSleeping = shouldSleepActivities();
info.isLetterboxDoubleTapEnabled = top != null
&& top.mLetterboxUiController.isLetterboxDoubleTapEducationEnabled();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 82b0086..aad1225 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -234,9 +234,6 @@
private @TransitionState int mState = STATE_PENDING;
private final ReadyTracker mReadyTracker = new ReadyTracker();
- // TODO(b/188595497): remove when not needed.
- /** @see RecentsAnimationController#mNavigationBarAttachedToApp */
- private boolean mNavBarAttachedToApp = false;
private int mRecentsDisplayId = INVALID_DISPLAY;
/** The delay for light bar appearance animation. */
@@ -690,6 +687,7 @@
if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully()
|| wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) {
mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
+ return;
}
if (mContainerFreezer == null) {
@@ -1780,7 +1778,7 @@
if (navWindow == null || navWindow.mToken == null) {
return;
}
- mNavBarAttachedToApp = true;
+ mController.mNavigationBarAttachedToApp = true;
navWindow.mToken.cancelAnimation();
final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
@@ -1802,8 +1800,10 @@
/** @see RecentsAnimationController#restoreNavigationBarFromApp */
void legacyRestoreNavigationBarFromApp() {
- if (!mNavBarAttachedToApp) return;
- mNavBarAttachedToApp = false;
+ if (!mController.mNavigationBarAttachedToApp) {
+ return;
+ }
+ mController.mNavigationBarAttachedToApp = false;
if (mRecentsDisplayId == INVALID_DISPLAY) {
Slog.e(TAG, "Reparented navigation bar without a valid display");
@@ -1836,6 +1836,11 @@
break;
}
+ final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
+ if (asyncRotationController != null) {
+ asyncRotationController.accept(navWindow);
+ }
+
if (animate) {
final NavBarFadeAnimationController controller =
new NavBarFadeAnimationController(dc);
@@ -1844,6 +1849,9 @@
// Reparent the SurfaceControl of nav bar token back.
t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
}
+
+ // To apply transactions.
+ dc.mWmService.scheduleAnimationLocked();
}
private void reportStartReasonsToLogger() {
@@ -2317,23 +2325,13 @@
task.fillTaskInfo(tinfo);
change.setTaskInfo(tinfo);
change.setRotationAnimation(getTaskRotationAnimation(task));
- final ActivityRecord topMostActivity = task.getTopMostActivity();
- change.setAllowEnterPip(topMostActivity != null
- && topMostActivity.checkEnterPictureInPictureAppOpsState());
final ActivityRecord topRunningActivity = task.topRunningActivity();
- if (topRunningActivity != null && task.mDisplayContent != null
- // Display won't be rotated for multi window Task, so the fixed rotation
- // won't be applied. This can happen when the windowing mode is changed
- // before the previous fixed rotation is applied.
- && (!task.inMultiWindowMode() || !topRunningActivity.inMultiWindowMode())) {
- // If Activity is in fixed rotation, its will be applied with the next rotation,
- // when the Task is still in the previous rotation.
- final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
- final int activityRotation = topRunningActivity.getWindowConfiguration()
- .getDisplayRotation();
- if (taskRotation != activityRotation) {
- change.setEndFixedRotation(activityRotation);
+ if (topRunningActivity != null) {
+ if (topRunningActivity.info.supportsPictureInPicture()) {
+ change.setAllowEnterPip(
+ topRunningActivity.checkEnterPictureInPictureAppOpsState());
}
+ setEndFixedRotationIfNeeded(change, task, topRunningActivity);
}
} else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
@@ -2441,6 +2439,48 @@
}
return animOptions;
}
+
+ private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change,
+ @NonNull Task task, @NonNull ActivityRecord taskTopRunning) {
+ if (!taskTopRunning.isVisibleRequested()) {
+ // Fixed rotation only applies to opening or changing activity.
+ return;
+ }
+ if (task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) {
+ // Display won't be rotated for multi window Task, so the fixed rotation won't be
+ // applied. This can happen when the windowing mode is changed before the previous
+ // fixed rotation is applied. Check both task and activity because the activity keeps
+ // fullscreen mode when the task is entering PiP.
+ return;
+ }
+ final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
+ final int activityRotation = taskTopRunning.getWindowConfiguration()
+ .getDisplayRotation();
+ // If the Activity uses fixed rotation, its rotation will be applied to display after
+ // the current transition is done, while the Task is still in the previous rotation.
+ if (taskRotation != activityRotation) {
+ change.setEndFixedRotation(activityRotation);
+ return;
+ }
+
+ // For example, the task is entering PiP so it no longer decides orientation. If the next
+ // orientation source (it could be an activity which was behind the PiP or launching to top)
+ // will change display rotation, then set the fixed rotation hint as well so the animation
+ // can consider the rotated position.
+ if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) {
+ return;
+ }
+ final WindowContainer<?> orientationSource =
+ taskTopRunning.mDisplayContent.getLastOrientationSource();
+ if (orientationSource == null) {
+ return;
+ }
+ final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation();
+ if (taskRotation != nextRotation) {
+ change.setEndFixedRotation(nextRotation);
+ }
+ }
+
/**
* Finds the top-most common ancestor of app targets.
*
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 0cb6f14..359b353 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -206,6 +206,11 @@
*/
boolean mBuildingFinishLayers = false;
+ /**
+ * Whether the surface of navigation bar token is reparented to an app.
+ */
+ boolean mNavigationBarAttachedToApp = false;
+
private boolean mAnimatingState = false;
final Handler mLoggerHandler = FgThread.getHandler();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4c3a86d..763c4d6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,6 +88,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
@@ -3589,6 +3590,7 @@
public void setCurrentUser(@UserIdInt int newUserId) {
synchronized (mGlobalLock) {
+ mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_OPEN, null);
mCurrentUserId = newUserId;
mPolicy.setCurrentUserLw(newUserId);
mKeyguardDisableHandler.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 3672820..d7d2b4e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -563,7 +563,7 @@
return;
}
- final long diff = lastLaunchTime - launchTime;
+ final long diff = launchTime - lastLaunchTime;
if (diff < RAPID_ACTIVITY_LAUNCH_MS) {
mRapidActivityLaunchCount++;
} else if (diff >= RESET_RAPID_ACTIVITY_LAUNCH_MS) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1ecc162..3ba4170 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -352,6 +352,8 @@
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
+ void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -963,6 +965,15 @@
checkAndClearExceptionFromCallback(env, "notifyDropWindow");
}
+void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) {
+ static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
+ sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false);
+ if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
+
+ mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
+}
+
void NativeInputManager::notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
const std::vector<float>& values) {
@@ -1159,6 +1170,8 @@
}
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
+ bool refresh = false;
+
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1166,14 +1179,18 @@
bool currentlyEnabled = it == mLocked.disabledInputDevices.end();
if (!enabled && currentlyEnabled) {
mLocked.disabledInputDevices.insert(deviceId);
+ refresh = true;
}
if (enabled && !currentlyEnabled) {
mLocked.disabledInputDevices.erase(deviceId);
+ refresh = true;
}
} // release lock
- mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::Change::ENABLED_STATE);
+ if (refresh) {
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::ENABLED_STATE);
+ }
}
void NativeInputManager::setShowTouches(bool enabled) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index f96ca58..7104a80 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -46,6 +46,8 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="luxThrottling" name="luxThrottling" minOccurs="0"
+ maxOccurs="1"/>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
@@ -137,6 +139,39 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="luxThrottling">
+ <xs:sequence>
+ <xs:element name="brightnessLimitMap" type="brightnessLimitMap"
+ maxOccurs="unbounded">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessLimitMap">
+ <xs:sequence>
+ <xs:element name="type" type="PredefinedBrightnessLimitNames">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- lux level from light sensor to screen brightness recommended max value map.
+ Screen brightness recommended max value is to highBrightnessMode.transitionPoint and must be below that -->
+ <xs:element name="map" type="nonNegativeFloatToFloatMap">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Predefined type names as defined by DisplayDeviceConfig.BrightnessLimitMapType -->
+ <xs:simpleType name="PredefinedBrightnessLimitNames">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="adaptive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
<xs:complexType name="highBrightnessMode">
<xs:all>
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
@@ -575,4 +610,27 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
+
+ <!-- generic types -->
+ <xs:complexType name="nonNegativeFloatToFloatPoint">
+ <xs:sequence>
+ <xs:element name="first" type="nonNegativeDecimal">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="second" type="nonNegativeDecimal">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="nonNegativeFloatToFloatMap">
+ <xs:sequence>
+ <xs:element name="point" type="nonNegativeFloatToFloatPoint" maxOccurs="unbounded">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ad6434e..507c9dc 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -26,6 +26,14 @@
method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
}
+ public class BrightnessLimitMap {
+ ctor public BrightnessLimitMap();
+ method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+ method @NonNull public final com.android.server.display.config.PredefinedBrightnessLimitNames getType();
+ method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -89,6 +97,7 @@
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.SensorDetails getLightSensor();
+ method public com.android.server.display.config.LuxThrottling getLuxThrottling();
method @Nullable public final String getName();
method public final com.android.server.display.config.SensorDetails getProxSensor();
method public com.android.server.display.config.DisplayQuirks getQuirks();
@@ -115,6 +124,7 @@
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
+ method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
method public final void setName(@Nullable String);
method public final void setProxSensor(com.android.server.display.config.SensorDetails);
method public void setQuirks(com.android.server.display.config.DisplayQuirks);
@@ -173,6 +183,11 @@
method public java.util.List<java.math.BigInteger> getItem();
}
+ public class LuxThrottling {
+ ctor public LuxThrottling();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
+ }
+
public class NitsMap {
ctor public NitsMap();
method public String getInterpolation();
@@ -180,6 +195,19 @@
method public void setInterpolation(String);
}
+ public class NonNegativeFloatToFloatMap {
+ ctor public NonNegativeFloatToFloatMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.NonNegativeFloatToFloatPoint> getPoint();
+ }
+
+ public class NonNegativeFloatToFloatPoint {
+ ctor public NonNegativeFloatToFloatPoint();
+ method @NonNull public final java.math.BigDecimal getFirst();
+ method @NonNull public final java.math.BigDecimal getSecond();
+ method public final void setFirst(@NonNull java.math.BigDecimal);
+ method public final void setSecond(@NonNull java.math.BigDecimal);
+ }
+
public class Point {
ctor public Point();
method @NonNull public final java.math.BigDecimal getNits();
@@ -188,6 +216,12 @@
method public final void setValue(@NonNull java.math.BigDecimal);
}
+ public enum PredefinedBrightnessLimitNames {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames _default;
+ enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames adaptive;
+ }
+
public class RefreshRateConfigs {
ctor public RefreshRateConfigs();
method public final java.math.BigInteger getDefaultPeakRefreshRate();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index c918fb8..9c1d765 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -101,7 +101,7 @@
private final UserManager mUserManager;
// TODO(b/256849338): add more granular locks
- private final Object mLock = new Object();
+ private final Object mLock;
/**
* Map of <userId, Map<policyKey, policyState>>
@@ -122,9 +122,11 @@
DevicePolicyEngine(
@NonNull Context context,
- @NonNull DeviceAdminServiceController deviceAdminServiceController) {
+ @NonNull DeviceAdminServiceController deviceAdminServiceController,
+ @NonNull Object lock) {
mContext = Objects.requireNonNull(context);
mDeviceAdminServiceController = Objects.requireNonNull(deviceAdminServiceController);
+ mLock = Objects.requireNonNull(lock);
mUserManager = mContext.getSystemService(UserManager.class);
mLocalPolicies = new SparseArray<>();
mGlobalPolicies = new HashMap<>();
@@ -152,8 +154,8 @@
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
if (policyDefinition.isNonCoexistablePolicy()) {
- setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
- value, userId, skipEnforcePolicy);
+ setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
+ enforcingAdmin, value, userId, skipEnforcePolicy);
return;
}
@@ -173,7 +175,7 @@
// the data structures.
if (!skipEnforcePolicy) {
if (policyChanged) {
- onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+ onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
boolean policyEnforced = Objects.equals(
localPolicyState.getCurrentResolvedPolicy(), value);
@@ -211,7 +213,7 @@
*
* <p>Passing a {@code null} value means the policy set by this admin should be removed.
*/
- private <V> void setNonCoexistableLocalPolicy(
+ private <V> void setNonCoexistableLocalPolicyLocked(
PolicyDefinition<V> policyDefinition,
PolicyState<V> localPolicyState,
EnforcingAdmin enforcingAdmin,
@@ -266,8 +268,8 @@
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
if (policyDefinition.isNonCoexistablePolicy()) {
- setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
- /* value= */ null, userId, /* skipEnforcePolicy= */ false);
+ setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
+ enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false);
return;
}
@@ -282,7 +284,7 @@
}
if (policyChanged) {
- onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+ onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
// For a removePolicy to be enforced, it means no current policy exists
@@ -348,7 +350,7 @@
/**
* Enforces the new policy and notifies relevant admins.
*/
- private <V> void onLocalPolicyChanged(
+ private <V> void onLocalPolicyChangedLocked(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
int userId) {
@@ -358,7 +360,7 @@
policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
// Send policy updates to admins who've set it locally
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
localPolicyState,
enforcingAdmin,
policyDefinition,
@@ -369,7 +371,7 @@
// Send policy updates to admins who've set it globally
if (hasGlobalPolicyLocked(policyDefinition)) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
globalPolicyState,
enforcingAdmin,
policyDefinition,
@@ -424,7 +426,7 @@
// the data structures.
if (!skipEnforcePolicy) {
if (policyChanged) {
- onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+ onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
boolean policyAppliedGlobally = Objects.equals(
@@ -473,7 +475,7 @@
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
- onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+ onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
applyGlobalPolicyOnUsersWithLocalPoliciesLocked(policyDefinition, enforcingAdmin,
@@ -499,7 +501,7 @@
/**
* Enforces the new policy globally and notifies relevant admins.
*/
- private <V> void onGlobalPolicyChanged(
+ private <V> void onGlobalPolicyChangedLocked(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
@@ -507,7 +509,7 @@
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
policyState,
enforcingAdmin,
policyDefinition,
@@ -552,7 +554,7 @@
policyDefinition,
localPolicyState.getCurrentResolvedPolicy(),
userId);
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
localPolicyState,
enforcingAdmin,
policyDefinition,
@@ -745,34 +747,35 @@
}
<V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) {
- Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
- for (PolicyKey policy : globalPolicies) {
- PolicyState<?> policyState = mGlobalPolicies.get(policy);
- if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
- PolicyDefinition<V> policyDefinition =
- (PolicyDefinition<V>) policyState.getPolicyDefinition();
- PolicyValue<V> policyValue =
- (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
- setGlobalPolicy(policyDefinition, newAdmin, policyValue);
- }
- }
-
- for (int i = 0; i < mLocalPolicies.size(); i++) {
- int userId = mLocalPolicies.keyAt(i);
- Set<PolicyKey> localPolicies = new HashSet<>(
- mLocalPolicies.get(userId).keySet());
- for (PolicyKey policy : localPolicies) {
- PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ synchronized (mLock) {
+ Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+ for (PolicyKey policy : globalPolicies) {
+ PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
PolicyDefinition<V> policyDefinition =
(PolicyDefinition<V>) policyState.getPolicyDefinition();
PolicyValue<V> policyValue =
(PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
- setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+ setGlobalPolicy(policyDefinition, newAdmin, policyValue);
+ }
+ }
+
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ int userId = mLocalPolicies.keyAt(i);
+ Set<PolicyKey> localPolicies = new HashSet<>(
+ mLocalPolicies.get(userId).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+ PolicyDefinition<V> policyDefinition =
+ (PolicyDefinition<V>) policyState.getPolicyDefinition();
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+ setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+ }
}
}
}
-
removePoliciesForAdmin(oldAdmin);
}
@@ -836,7 +839,7 @@
mLocalPolicies.get(userId).put(
policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
- return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
+ return getPolicyStateLocked(mLocalPolicies.get(userId), policyDefinition);
}
private <V> void removeLocalPolicyStateLocked(
@@ -858,14 +861,14 @@
mGlobalPolicies.put(
policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
- return getPolicyState(mGlobalPolicies, policyDefinition);
+ return getPolicyStateLocked(mGlobalPolicies, policyDefinition);
}
private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
mGlobalPolicies.remove(policyDefinition.getPolicyKey());
}
- private static <V> PolicyState<V> getPolicyState(
+ private static <V> PolicyState<V> getPolicyStateLocked(
Map<PolicyKey, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
try {
// This will not throw an exception because policyDefinition is of type V, so unless
@@ -935,7 +938,7 @@
}
// TODO(b/261430877): Finalise the decision on which admins to send the updates to.
- private <V> void sendPolicyChangedToAdmins(
+ private <V> void sendPolicyChangedToAdminsLocked(
PolicyState<V> policyState,
EnforcingAdmin callingAdmin,
PolicyDefinition<V> policyDefinition,
@@ -1210,17 +1213,19 @@
if (parentInfo == null || parentInfo.getUserHandle().getIdentifier() == userId) {
return;
}
- if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) {
- return;
- }
- for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
- parentInfo.getUserHandle().getIdentifier()).entrySet()) {
- enforcePolicyOnUser(userId, entry.getValue());
+ synchronized (mLock) {
+ if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) {
+ return;
+ }
+ for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
+ parentInfo.getUserHandle().getIdentifier()).entrySet()) {
+ enforcePolicyOnUserLocked(userId, entry.getValue());
+ }
}
});
}
- private <V> void enforcePolicyOnUser(int userId, PolicyState<V> policyState) {
+ private <V> void enforcePolicyOnUserLocked(int userId, PolicyState<V> policyState) {
if (!policyState.getPolicyDefinition().isInheritable()) {
return;
}
@@ -1239,26 +1244,28 @@
*/
@NonNull
DevicePolicyState getDevicePolicyState() {
- Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
- new HashMap<>();
- for (int i = 0; i < mLocalPolicies.size(); i++) {
- UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
- policies.put(user, new HashMap<>());
- for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
- policies.get(user).put(
- policyKey,
- mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+ synchronized (mLock) {
+ Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
+ new HashMap<>();
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
+ policies.put(user, new HashMap<>());
+ for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
+ policies.get(user).put(
+ policyKey,
+ mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+ }
}
- }
- if (!mGlobalPolicies.isEmpty()) {
- policies.put(UserHandle.ALL, new HashMap<>());
- for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
- policies.get(UserHandle.ALL).put(
- policyKey,
- mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+ if (!mGlobalPolicies.isEmpty()) {
+ policies.put(UserHandle.ALL, new HashMap<>());
+ for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
+ policies.get(UserHandle.ALL).put(
+ policyKey,
+ mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+ }
}
+ return new DevicePolicyState(policies);
}
- return new DevicePolicyState(policies);
}
@@ -1266,23 +1273,25 @@
* Removes all local and global policies set by that admin.
*/
void removePoliciesForAdmin(EnforcingAdmin admin) {
- Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
- for (PolicyKey policy : globalPolicies) {
- PolicyState<?> policyState = mGlobalPolicies.get(policy);
- if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
- removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
- }
- }
-
- for (int i = 0; i < mLocalPolicies.size(); i++) {
- Set<PolicyKey> localPolicies = new HashSet<>(
- mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
- for (PolicyKey policy : localPolicies) {
- PolicyState<?> policyState = mLocalPolicies.get(
- mLocalPolicies.keyAt(i)).get(policy);
+ synchronized (mLock) {
+ Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+ for (PolicyKey policy : globalPolicies) {
+ PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
- removeLocalPolicy(
- policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+ removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
+ }
+ }
+
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ Set<PolicyKey> localPolicies = new HashSet<>(
+ mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(
+ mLocalPolicies.keyAt(i)).get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ removeLocalPolicy(
+ policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+ }
}
}
}
@@ -1292,23 +1301,25 @@
* Removes all local policies for the provided {@code userId}.
*/
private void removeLocalPoliciesForUser(int userId) {
- if (!mLocalPolicies.contains(userId)) {
- // No policies on user
- return;
- }
-
- Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
- for (PolicyKey policy : localPolicies) {
- PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
- Set<EnforcingAdmin> admins = new HashSet<>(
- policyState.getPoliciesSetByAdmins().keySet());
- for (EnforcingAdmin admin : admins) {
- removeLocalPolicy(
- policyState.getPolicyDefinition(), admin, userId);
+ synchronized (mLock) {
+ if (!mLocalPolicies.contains(userId)) {
+ // No policies on user
+ return;
}
- }
- mLocalPolicies.remove(userId);
+ Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ Set<EnforcingAdmin> admins = new HashSet<>(
+ policyState.getPoliciesSetByAdmins().keySet());
+ for (EnforcingAdmin admin : admins) {
+ removeLocalPolicy(
+ policyState.getPolicyDefinition(), admin, userId);
+ }
+ }
+
+ mLocalPolicies.remove(userId);
+ }
}
/**
@@ -1376,7 +1387,7 @@
*/
private void updateDeviceAdminServiceOnPolicyRemoveLocked(
@NonNull EnforcingAdmin enforcingAdmin) {
- if (doesAdminHavePolicies(enforcingAdmin)) {
+ if (doesAdminHavePoliciesLocked(enforcingAdmin)) {
return;
}
int userId = enforcingAdmin.getUserId();
@@ -1399,7 +1410,7 @@
/* actionForLog= */ "policy-removed");
}
- private boolean doesAdminHavePolicies(@NonNull EnforcingAdmin enforcingAdmin) {
+ private boolean doesAdminHavePoliciesLocked(@NonNull EnforcingAdmin enforcingAdmin) {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
@@ -1420,13 +1431,17 @@
@NonNull
private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
- return mEnforcingAdmins.contains(userId)
- ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+ synchronized (mLock) {
+ return mEnforcingAdmins.contains(userId)
+ ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+ }
}
private void write() {
- Log.d(TAG, "Writing device policies to file.");
- new DevicePoliciesReaderWriter().writeToFileLocked();
+ synchronized (mLock) {
+ Log.d(TAG, "Writing device policies to file.");
+ new DevicePoliciesReaderWriter().writeToFileLocked();
+ }
}
// TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated
@@ -1436,11 +1451,11 @@
synchronized (mLock) {
clear();
new DevicePoliciesReaderWriter().readFromFileLocked();
- reapplyAllPolicies();
+ reapplyAllPoliciesLocked();
}
}
- private <V> void reapplyAllPolicies() {
+ private <V> void reapplyAllPoliciesLocked() {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
// Policy definition and value will always be of the same type
@@ -1470,10 +1485,8 @@
* <p>Note that this doesn't clear any enforcements, it only clears the data structures.
*/
void clearAllPolicies() {
- synchronized (mLock) {
- clear();
- write();
- }
+ clear();
+ write();
}
private void clear() {
synchronized (mLock) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 88e57f3..a452328 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2093,7 +2093,8 @@
mUserData = new SparseArray<>();
mOwners = makeOwners(injector, pathProvider);
- mDevicePolicyEngine = new DevicePolicyEngine(mContext, mDeviceAdminServiceController);
+ mDevicePolicyEngine = new DevicePolicyEngine(
+ mContext, mDeviceAdminServiceController, getLockObject());
if (!mHasFeature) {
// Skip the rest of the initialization
@@ -13601,6 +13602,9 @@
: SecurityLog.TAG_USER_RESTRICTION_REMOVED;
SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
}
+
+ Slogf.i(LOG_TAG, "Changing user restriction %s to: %b caller: %s",
+ key, enabled, caller.toString());
}
private void saveUserRestrictionsLocked(int userId) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 70f0cbd..f29b07f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2194,12 +2194,13 @@
}
// WallpaperEffectsGeneration manager service
- // TODO (b/135218095): Use deviceHasConfigString(context,
- // R.string.config_defaultWallpaperEffectsGenerationService)
- t.traceBegin("StartWallpaperEffectsGenerationService");
- mSystemServiceManager.startService(
+ if (deviceHasConfigString(context,
+ R.string.config_defaultWallpaperEffectsGenerationService)) {
+ t.traceBegin("StartWallpaperEffectsGenerationService");
+ mSystemServiceManager.startService(
WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ t.traceEnd();
+ }
t.traceBegin("StartAudioService");
if (!isArc) {
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index 0927c7a..c1d137f 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -130,17 +130,6 @@
}
} as IndexedMap<String, IndexedListSet<String>>
- fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
- getState {
- with(policy) { getDecision(subject, `object`) }
- }
-
- fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
- mutateState {
- with(policy) { setDecision(subject, `object`, decision) }
- }
- }
-
internal fun onUserAdded(userId: Int) {
mutateState {
with(policy) { onUserAdded(userId) }
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 44a29e5..c60f184 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -56,13 +56,6 @@
"Scheme policy for $subjectScheme and $objectScheme does not exist"
}
- fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int =
- with(getSchemePolicy(subject, `object`)) { getDecision(subject, `object`) }
-
- fun MutateStateScope.setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
- with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
- }
-
fun initialize(
state: MutableAccessState,
userIds: IntSet,
@@ -494,14 +487,6 @@
abstract val objectScheme: String
- abstract fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int
-
- abstract fun MutateStateScope.setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int
- )
-
open fun GetStateScope.onStateMutated() {}
open fun MutateStateScope.onInitialized() {}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 4b4f726..c02fe4d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,8 +17,6 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
-import com.android.server.permission.access.AccessUri
-import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -40,22 +38,6 @@
override val subjectScheme: String
get() = UidUri.SCHEME
- override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
- subject as UidUri
- `object` as AppOpUri
- return getAppOpMode(subject.appId, subject.userId, `object`.appOpName)
- }
-
- override fun MutateStateScope.setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int
- ) {
- subject as UidUri
- `object` as AppOpUri
- setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
- }
-
override fun GetStateScope.onStateMutated() {
onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index d384133..5398a57 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,8 +17,6 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
-import com.android.server.permission.access.AccessUri
-import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -40,22 +38,6 @@
override val subjectScheme: String
get() = PackageUri.SCHEME
- override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
- subject as PackageUri
- `object` as AppOpUri
- return getAppOpMode(subject.packageName, subject.userId, `object`.appOpName)
- }
-
- override fun MutateStateScope.setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int
- ) {
- subject as PackageUri
- `object` as AppOpUri
- setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
- }
-
override fun GetStateScope.onStateMutated() {
onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
index 85326c3..dc9bae3 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
@@ -64,6 +64,18 @@
operator fun <T> IndexedList<T>.plus(element: T): MutableIndexedList<T> =
toMutable().apply { this += element }
+// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
+inline fun <T> IndexedList<T>.reduceIndexed(
+ initialValue: Int,
+ accumulator: (Int, Int, T) -> Int
+): Int {
+ var value = initialValue
+ forEachIndexed { index, element ->
+ value = accumulator(value, index, element)
+ }
+ return value
+}
+
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MutableIndexedList<T>.minusAssign(element: T) {
remove(element)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
index 950d9aa..13fc141 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
@@ -64,6 +64,18 @@
operator fun <T> IndexedListSet<T>.plus(element: T): MutableIndexedListSet<T> =
toMutable().apply { this += element }
+// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
+inline fun <T> IndexedListSet<T>.reduceIndexed(
+ initialValue: Int,
+ accumulator: (Int, Int, T) -> Int
+): Int {
+ var value = initialValue
+ forEachIndexed { index, element ->
+ value = accumulator(value, index, element)
+ }
+ return value
+}
+
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MutableIndexedListSet<T>.minusAssign(element: T) {
remove(element)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt
index 2cc1b2a..5959ec6 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt
@@ -80,6 +80,6 @@
add(element)
}
-operator fun <T> MutableIndexedSet<T>.plusAssign(list: List<T>) {
- list.forEachIndexed { _, it -> this += it }
+operator fun <T> MutableIndexedSet<T>.plusAssign(collection: Collection<T>) {
+ collection.forEach { this += it }
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
index fffc703..2c8175b 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
@@ -74,8 +74,8 @@
if (packageName !in externalState.packageStates &&
packageName !in externalState.disabledSystemPackageStates) {
Slog.w(
- LOG_TAG,
- "Dropping permission with unknown package $packageName when parsing permissions"
+ LOG_TAG, "Dropping permission ${permission.name} from unknown package" +
+ " $packageName when parsing permissions"
)
permissions.removeAt(permissionIndex)
systemState.requestWriteMode(WriteMode.ASYNCHRONOUS)
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 9610188e..b0a964a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -27,7 +27,6 @@
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -67,22 +66,6 @@
override val objectScheme: String
get() = PermissionUri.SCHEME
- override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
- subject as UidUri
- `object` as PermissionUri
- return getPermissionFlags(subject.appId, subject.userId, `object`.permissionName)
- }
-
- override fun MutateStateScope.setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int
- ) {
- subject as UidUri
- `object` as PermissionUri
- setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision)
- }
-
override fun GetStateScope.onStateMutated() {
onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
}
@@ -216,16 +199,17 @@
}
val androidPackage = packageState.androidPackage ?: return
val appId = packageState.appId
- androidPackage.requestedPermissions.forEachIndexed { _, permissionName ->
+ androidPackage.requestedPermissions.forEach { permissionName ->
val permission = newState.systemState.permissions[permissionName]
- ?: return@forEachIndexed
+ ?: return@forEach
if (!permission.isHardOrSoftRestricted) {
- return@forEachIndexed
+ return@forEach
}
- val isRequestedBySystemPackage =
- anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
+ val isRequestedBySystemPackage = anyPackageInAppId(appId) {
+ it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
+ }
if (isRequestedBySystemPackage) {
- return@forEachIndexed
+ return@forEach
}
val oldFlags = getPermissionFlags(appId, userId, permissionName)
var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
@@ -258,21 +242,22 @@
val packageState = newState.externalState.packageStates[packageName] ?: return
val androidPackage = packageState.androidPackage ?: return
val appId = packageState.appId
- androidPackage.requestedPermissions.forEachIndexed { _, permissionName ->
+ androidPackage.requestedPermissions.forEach { permissionName ->
val permission = newState.systemState.permissions[permissionName]
- ?: return@forEachIndexed
+ ?: return@forEach
if (permission.isRemoved) {
- return@forEachIndexed
+ return@forEach
}
- val isRequestedByOtherPackages = anyRequestingPackageInAppId(appId, permissionName) {
- it.packageName != packageName
+ val isRequestedByOtherPackages = anyPackageInAppId(appId) {
+ it.packageName != packageName &&
+ permissionName in it.androidPackage!!.requestedPermissions
}
if (isRequestedByOtherPackages) {
- return@forEachIndexed
+ return@forEach
}
val oldFlags = getPermissionFlags(appId, userId, permissionName)
if (oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
- return@forEachIndexed
+ return@forEach
}
var newFlags = oldFlags
newFlags = if (
@@ -444,7 +429,7 @@
// It's a config permission and has no owner, take ownership now.
oldPermission.copy(
permissionInfo = newPermissionInfo, isReconciled = true,
- appId = packageState.appId
+ type = Permission.TYPE_MANIFEST, appId = packageState.appId
)
} else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) {
Slog.w(
@@ -478,11 +463,12 @@
val isPermissionGroupChanged = newPermissionInfo.isRuntime &&
newPermissionInfo.group != null &&
newPermissionInfo.group != oldPermission.groupName
- val isPermissionTypeChanged = oldPermission.type != Permission.TYPE_CONFIG && (
- (newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
- (newPermissionInfo.isInternal && !oldPermission.isInternal)
- )
- if (isPermissionGroupChanged || isPermissionTypeChanged) {
+ val isPermissionProtectionChanged =
+ oldPermission.type != Permission.TYPE_CONFIG && (
+ (newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
+ (newPermissionInfo.isInternal && !oldPermission.isInternal)
+ )
+ if (isPermissionGroupChanged || isPermissionProtectionChanged) {
newState.externalState.userIds.forEachIndexed { _, userId ->
newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
if (isPermissionGroupChanged) {
@@ -497,11 +483,11 @@
" to ${newPermissionInfo.group}"
)
}
- if (isPermissionTypeChanged) {
+ if (isPermissionProtectionChanged) {
Slog.w(
LOG_TAG, "Revoking permission $permissionName for" +
" appId $appId and userId $userId as the permission" +
- " type changed."
+ " protection changed."
)
}
setPermissionFlags(appId, userId, permissionName, 0)
@@ -517,7 +503,7 @@
if (oldPermission != null) {
oldPermission.copy(
permissionInfo = newPermissionInfo, isReconciled = true,
- appId = packageState.appId
+ type = Permission.TYPE_MANIFEST, appId = packageState.appId
)
} else {
Permission(
@@ -530,8 +516,17 @@
newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission
} else {
newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
+ val isPermissionChanged = oldPermission == null ||
+ newPackageName != oldPermission.packageName ||
+ newPermission.protectionLevel != oldPermission.protectionLevel || (
+ oldPermission.isReconciled && newPermission.isRuntime &&
+ newPermission.groupName != null &&
+ newPermission.groupName != oldPermission.groupName
+ )
+ if (isPermissionChanged) {
+ changedPermissionNames += permissionName
+ }
}
- changedPermissionNames += permissionName
}
}
@@ -628,36 +623,59 @@
}
private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) {
+ val hasOldPackage = appId in oldState.externalState.appIdPackageNames &&
+ anyPackageInAppId(appId, oldState) { true }
+ if (!hasOldPackage) {
+ // Don't revoke anything if this isn't a package update, i.e. if information about the
+ // old package isn't available. Notably, this also means skipping packages changed via
+ // OTA, but the revocation here is also mostly for normal apps and there's no way to get
+ // information about the package before OTA anyway.
+ return
+ }
+
// If the app is updated, and has scoped storage permissions, then it is possible that the
// app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
- newState.userStates.forEachIndexed { _, userId, userState ->
- userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
- _, permissionName, oldFlags ->
- if (permissionName !in STORAGE_AND_MEDIA_PERMISSIONS || oldFlags == 0) {
- return@forEachReversedIndexed
- }
- val oldTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, oldState)
- val newTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, newState)
- val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
- newTargetSdkVersion < Build.VERSION_CODES.Q
- val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
- newTargetSdkVersion >= Build.VERSION_CODES.Q
- val oldIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
- appId, permissionName, oldState
- ) { it.androidPackage!!.isRequestLegacyExternalStorage }
- val newIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
- appId, permissionName, newState
- ) { it.androidPackage!!.isRequestLegacyExternalStorage }
- val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
- !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
- if ((isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded) &&
- oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
- Slog.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
- " $appId and user: $userId")
- val newFlags = oldFlags andInv (
- PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
- )
- setPermissionFlags(appId, userId, permissionName, newFlags)
+ val oldTargetSdkVersion =
+ reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) {
+ targetSdkVersion, packageState ->
+ targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
+ }
+ val newTargetSdkVersion =
+ reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) {
+ targetSdkVersion, packageState ->
+ targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
+ }
+ @Suppress("ConvertTwoComparisonsToRangeCheck")
+ val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
+ newTargetSdkVersion < Build.VERSION_CODES.Q
+ @Suppress("ConvertTwoComparisonsToRangeCheck")
+ val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
+ newTargetSdkVersion >= Build.VERSION_CODES.Q
+ val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) {
+ it.androidPackage!!.isRequestLegacyExternalStorage
+ }
+ val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) {
+ it.androidPackage!!.isRequestLegacyExternalStorage
+ }
+ val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
+ !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
+ val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage ||
+ isTargetSdkVersionDowngraded
+ if (shouldRevokeStorageAndMediaPermissions) {
+ newState.userStates.forEachIndexed { _, userId, userState ->
+ userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
+ _, permissionName, oldFlags ->
+ if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
+ oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
+ Slog.v(
+ LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
+ " $appId and user: $userId"
+ )
+ val newFlags = oldFlags andInv (
+ PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
+ )
+ setPermissionFlags(appId, userId, permissionName, newFlags)
+ }
}
}
}
@@ -670,8 +688,9 @@
val externalState = newState.externalState
externalState.userIds.forEachIndexed { _, userId ->
externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
- val isPermissionRequested =
- anyRequestingPackageInAppId(appId, permissionName) { true }
+ val isPermissionRequested = anyPackageInAppId(appId) {
+ permissionName in it.androidPackage!!.requestedPermissions
+ }
if (isPermissionRequested) {
evaluatePermissionState(appId, userId, permissionName, installedPackageState)
}
@@ -695,7 +714,7 @@
userId: Int,
installedPackageState: PackageState?
) {
- packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
+ packageState.androidPackage?.requestedPermissions?.forEach { permissionName ->
evaluatePermissionState(
packageState.appId, userId, permissionName, installedPackageState
)
@@ -709,8 +728,21 @@
installedPackageState: PackageState?
) {
val packageNames = newState.externalState.appIdPackageNames[appId]!!
- val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
- newState.externalState.packageStates[packageName]!!.androidPackage == null
+ // Repeatedly checking whether a permission is requested can actually be costly, so we cache
+ // the result for this method which is frequently called during boot, instead of calling
+ // anyPackageInAppId() and checking requested permissions multiple times.
+ val requestingPackageStates = MutableIndexedList<PackageState>()
+ var hasMissingPackage = false
+ packageNames.forEachIndexed { _, packageName ->
+ val packageState = newState.externalState.packageStates[packageName]!!
+ val androidPackage = packageState.androidPackage
+ if (androidPackage != null) {
+ if (permissionName in androidPackage.requestedPermissions) {
+ requestingPackageStates += packageState
+ }
+ } else {
+ hasMissingPackage = true
+ }
}
if (packageNames.size == 1 && hasMissingPackage) {
// For non-shared-user packages with missing androidPackage, skip evaluation.
@@ -734,8 +766,8 @@
val isRequestedByInstalledPackage = installedPackageState != null &&
permissionName in installedPackageState.androidPackage!!.requestedPermissions
val isRequestedBySystemPackage =
- anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
- val isCompatibilityPermission = anyRequestingPackageInAppId(appId, permissionName) {
+ requestingPackageStates.anyIndexed { _, it -> it.isSystem }
+ val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it ->
isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
}
// If this is an existing, non-system package,
@@ -760,20 +792,17 @@
// Keep the non-runtime permission grants for shared UID with missing androidPackage
PermissionFlags.PROTECTION_GRANTED
} else {
- val mayGrantByPrivileged = !permission.isPrivileged || (
- anyRequestingPackageInAppId(appId, permissionName) {
+ val mayGrantByPrivileged = !permission.isPrivileged ||
+ requestingPackageStates.anyIndexed { _, it ->
checkPrivilegedPermissionAllowlist(it, permission)
}
- )
- val shouldGrantBySignature = permission.isSignature && (
- anyRequestingPackageInAppId(appId, permissionName) {
+ val shouldGrantBySignature = permission.isSignature &&
+ requestingPackageStates.anyIndexed { _, it ->
shouldGrantPermissionBySignature(it, permission)
}
- )
- val shouldGrantByProtectionFlags =
- anyRequestingPackageInAppId(appId, permissionName) {
- shouldGrantPermissionByProtectionFlags(it, permission)
- }
+ val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it ->
+ shouldGrantPermissionByProtectionFlags(it, permission)
+ }
if (mayGrantByPrivileged &&
(shouldGrantBySignature || shouldGrantByProtectionFlags)) {
PermissionFlags.PROTECTION_GRANTED
@@ -803,7 +832,12 @@
} else if (permission.isRuntime) {
var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
- if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
+ val targetSdkVersion =
+ requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) {
+ targetSdkVersion, _, packageState ->
+ targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.M) {
if (permission.isRuntimeOnly) {
// Different from the old implementation, which simply skips a runtime-only
// permission, we now only allow holding on to the restriction related flags,
@@ -835,7 +869,7 @@
val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
permissionName in NOTIFICATIONS_PERMISSIONS
- val isImplicitPermission = anyRequestingPackageInAppId(appId, permissionName) {
+ val isImplicitPermission = requestingPackageStates.anyIndexed { _, it ->
permissionName in it.androidPackage!!.implicitPermissions
}
val sourcePermissions = newState.externalState
@@ -1106,30 +1140,15 @@
}
}
- private fun MutateStateScope.getAppIdTargetSdkVersion(
+ private inline fun MutateStateScope.anyPackageInAppId(
appId: Int,
- permissionName: String,
- state: AccessState = newState
- ): Int {
- var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
- forEachRequestingPackageInAppId(appId, permissionName, state) {
- targetSdkVersion = targetSdkVersion.coerceAtMost(it.androidPackage!!.targetSdkVersion)
- }
- return targetSdkVersion
- }
-
- private inline fun MutateStateScope.anyRequestingPackageInAppId(
- appId: Int,
- permissionName: String,
state: AccessState = newState,
predicate: (PackageState) -> Boolean
): Boolean {
val packageNames = state.externalState.appIdPackageNames[appId]!!
return packageNames.anyIndexed { _, packageName ->
val packageState = state.externalState.packageStates[packageName]!!
- val androidPackage = packageState.androidPackage
- androidPackage != null && permissionName in androidPackage.requestedPermissions &&
- predicate(packageState)
+ packageState.androidPackage != null && predicate(packageState)
}
}
@@ -1147,18 +1166,20 @@
}
}
- private inline fun MutateStateScope.forEachRequestingPackageInAppId(
+ // Using Int instead of <T> to avoid autoboxing, since we only have the use case for Int.
+ private inline fun MutateStateScope.reducePackageInAppId(
appId: Int,
- permissionName: String,
+ initialValue: Int,
state: AccessState = newState,
- action: (PackageState) -> Unit
- ) {
+ accumulator: (Int, PackageState) -> Int
+ ): Int {
val packageNames = state.externalState.appIdPackageNames[appId]!!
- packageNames.forEachIndexed { _, packageName ->
+ return packageNames.reduceIndexed(initialValue) { value, _, packageName ->
val packageState = state.externalState.packageStates[packageName]!!
- val androidPackage = packageState.androidPackage
- if (androidPackage != null && permissionName in androidPackage.requestedPermissions) {
- action(packageState)
+ if (packageState.androidPackage != null) {
+ accumulator(value, packageState)
+ } else {
+ value
}
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5
index 138b611..3683dca 100644
--- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5
+++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5
Binary files differ
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index ca4a404..dc92376 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -66,7 +66,6 @@
import android.test.AndroidTestCase;
import android.util.Log;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
@@ -2507,7 +2506,6 @@
}
@LargeTest
- @FlakyTest(bugId = 283797480)
public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
// checkSignatures should be successful when both apps have been signed with the same
// rotated key since the initial signature comparison between the two apps should
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index c7fb32c..0eac4e6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -231,8 +231,8 @@
assertSame(deserialized.getPackageName(), deserialized2.getPackageName());
assertSame(deserialized.getPermission(),
deserialized2.getPermission());
- assertSame(deserialized.getRequestedPermissions().get(0),
- deserialized2.getRequestedPermissions().get(0));
+ assertSame(deserialized.getRequestedPermissions().iterator().next(),
+ deserialized2.getRequestedPermissions().iterator().next());
List<String> protectedBroadcastsOne = new ArrayList<>(1);
protectedBroadcastsOne.addAll(deserialized.getProtectedBroadcasts());
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 3200871..edab1d6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -623,7 +623,6 @@
expect.that(after.longVersionCode).isEqualTo(38654705667)
expect.that(after.requestedPermissions)
.containsExactlyElementsIn(after.usesPermissions.map { it.name })
- .inOrder()
expect.that(after.mimeGroups).containsExactly(
"TestActivityName/mimeGroup",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index a614c4d..2bc66ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -27,6 +27,7 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.util.DebugUtils.valueToString;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -742,24 +743,24 @@
broadcastIntent(intent1, null, true);
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
- StickyBroadcast.create(intent1, false, Process.myUid()));
+ StickyBroadcast.create(intent1, false, Process.myUid(), PROCESS_STATE_UNKNOWN));
assertNull(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER));
assertNull(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER));
broadcastIntent(intent2, options.toBundle(), true);
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
- StickyBroadcast.create(intent1, false, Process.myUid()));
+ StickyBroadcast.create(intent1, false, Process.myUid(), PROCESS_STATE_UNKNOWN));
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER),
- StickyBroadcast.create(intent2, true, Process.myUid()));
+ StickyBroadcast.create(intent2, true, Process.myUid(), PROCESS_STATE_UNKNOWN));
assertNull(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER));
broadcastIntent(intent3, null, true);
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
- StickyBroadcast.create(intent1, false, Process.myUid()));
+ StickyBroadcast.create(intent1, false, Process.myUid(), PROCESS_STATE_UNKNOWN));
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER),
- StickyBroadcast.create(intent2, true, Process.myUid()));
+ StickyBroadcast.create(intent2, true, Process.myUid(), PROCESS_STATE_UNKNOWN));
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER),
- StickyBroadcast.create(intent3, false, Process.myUid()));
+ StickyBroadcast.create(intent3, false, Process.myUid(), PROCESS_STATE_UNKNOWN));
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8eac804..1542112 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
@@ -246,7 +248,7 @@
return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
- BackgroundStartPrivileges.NONE, false, null);
+ BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
}
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
@@ -1092,6 +1094,17 @@
verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
verifyPendingRecords(redQueue, List.of(screenOff));
verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), resultTo, false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_resultTo_diffReceivers");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), resultTo, false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
}
@Test
@@ -1277,6 +1290,36 @@
}
@Test
+ public void testDeliveryGroupPolicy_merged_multipleReceivers() {
+ final long now = SystemClock.elapsedRealtime();
+ final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast1 = createDropboxBroadcast(
+ "TAG_A", now, 2);
+ final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast2 = createDropboxBroadcast(
+ "TAG_A", now + 1000, 4);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast1.first,
+ dropboxEntryBroadcast1.second,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_RED, CLASS_RED)),
+ false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast2.first,
+ dropboxEntryBroadcast2.second,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_RED, CLASS_RED)),
+ false));
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+
+ verifyPendingRecords(greenQueue,
+ List.of(dropboxEntryBroadcast1.first, dropboxEntryBroadcast2.first));
+ verifyPendingRecords(redQueue,
+ List.of(dropboxEntryBroadcast1.first, dropboxEntryBroadcast2.first));
+ }
+
+ @Test
public void testDeliveryGroupPolicy_sameAction_differentMatchingCriteria() {
final Intent closeSystemDialogs1 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
final BroadcastOptions optionsCloseSystemDialog1 = BroadcastOptions.makeBasic()
@@ -1407,7 +1450,7 @@
eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
- anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
+ anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
times(1));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 03231ec..0f75ea5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.UserHandle.USER_SYSTEM;
@@ -611,7 +612,7 @@
callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
AppOpsManager.OP_NONE, options, receivers, callerApp, resultTo,
Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId,
- BackgroundStartPrivileges.NONE, false, null);
+ BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
}
private void assertHealth() {
@@ -1599,7 +1600,7 @@
null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(),
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, null,
Activity.RESULT_OK, null, null, false, false, false, UserHandle.USER_SYSTEM,
- backgroundStartPrivileges, false, null);
+ backgroundStartPrivileges, false, null, PROCESS_STATE_UNKNOWN);
enqueueBroadcast(r);
waitForIdle();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 08952ea..f0efb79 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
import static android.content.Intent.ACTION_LOCKED_BOOT_COMPLETED;
@@ -958,7 +959,8 @@
userId,
BackgroundStartPrivileges.NONE,
false /* timeoutExempt */,
- filterExtrasForReceiver);
+ filterExtrasForReceiver,
+ PROCESS_STATE_UNKNOWN);
}
private static int getAppId(int i) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 24bc57e..646f486 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -38,6 +38,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -54,6 +55,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.permission.PermissionManager;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -119,6 +121,8 @@
@Before
public void setUp() {
+ assumeFalse(PermissionManager.USE_ACCESS_CHECKING_SERVICE);
+
mStorageFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
mRecentAccessesFile = new File(sContext.getFilesDir(), APP_OPS_ACCESSES_FILENAME);
mStorageFile.delete();
@@ -138,6 +142,11 @@
@After
public void tearDown() {
+ // @After methods are still executed even if there's assumption failure in @Before.
+ if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+ return;
+ }
+
mAppOpsService.shutdown();
mMockingSession.finishMocking();
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 c5ff8cc..dd23d9f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -28,6 +28,7 @@
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -148,6 +149,9 @@
// Used in JobConcurrencyManager.
doReturn(mock(UserManagerInternal.class))
.when(() -> LocalServices.getService(UserManagerInternal.class));
+ // Used in JobStatus.
+ doReturn(mock(JobSchedulerInternal.class))
+ .when(() -> LocalServices.getService(JobSchedulerInternal.class));
// Called via IdleController constructor.
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getResources()).thenReturn(mock(Resources.class));
@@ -168,6 +172,8 @@
JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ // Make sure the uptime is at least 24 hours so that tests that rely on high uptime work.
+ sUptimeMillisClock = getAdvancedClock(sUptimeMillisClock, 24 * HOUR_IN_MILLIS);
// Called by DeviceIdlenessTracker
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
@@ -313,6 +319,260 @@
}
@Test
+ public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled() {
+ JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // Safeguards disabled -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 UIJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 UIJ timeouts. Safeguards disabled -> no penalties.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 EJ timeouts. Safeguards disabled -> no penalties.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 Reg timeouts. Safeguards disabled -> no penalties.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+ }
+
+ @Test
+ public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled() {
+ JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // No timeouts -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 UIJ timeout. No execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // Not a timeout -> 1 UIJ timeout. No execution penalty yet.
+ jobUij.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 UIJ timeouts. Min execution penalty only for UIJs.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 EJ timeouts. Max execution penalty for EJs.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 Reg timeouts. Max execution penalty for regular jobs.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+ }
+
+ @Test
public void testGetMaxJobExecutionTimeMs() {
JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
createJobInfo(10)
@@ -327,7 +587,7 @@
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
- .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+ .when(tareController).getMaxJobExecutionTimeMsLocked(any());
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
@@ -337,6 +597,306 @@
mService.getMaxJobExecutionTimeMs(jobUIDT));
}
+ @Test
+ public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled() {
+ JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ QuotaController quotaController = mService.getQuotaController();
+ spyOn(quotaController);
+ TareController tareController = mService.getTareController();
+ spyOn(tareController);
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(tareController).getMaxJobExecutionTimeMsLocked(any());
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // Safeguards disabled -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 UIJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 UIJ timeouts. Safeguards disabled -> no penalties.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 EJ timeouts. Safeguards disabled -> no penalties.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 Reg timeouts. Safeguards disabled -> no penalties.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+ }
+
+ @Test
+ public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled() {
+ JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ QuotaController quotaController = mService.getQuotaController();
+ spyOn(quotaController);
+ TareController tareController = mService.getTareController();
+ spyOn(tareController);
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(tareController).getMaxJobExecutionTimeMsLocked(any());
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // No timeouts -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 UIJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // Not a timeout -> 1 UIJ timeout. No max execution penalty yet.
+ jobUij.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 UIJ timeouts. Max execution penalty only for UIJs.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // Not a timeout -> 1 EJ timeout. No max execution penalty yet.
+ jobEj.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 EJ timeouts. Max execution penalty for EJs.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // Not a timeout -> 1 reg timeout. No max execution penalty yet.
+ jobReg.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 Reg timeouts. Max execution penalty for regular jobs.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+ }
+
/**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
@@ -1226,6 +1786,7 @@
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1249,6 +1810,7 @@
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1270,6 +1832,7 @@
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1292,6 +1855,7 @@
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1315,6 +1879,7 @@
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(false).build();
final JobWorkItem item = new JobWorkItem.Builder().build();
@@ -1337,6 +1902,7 @@
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
final JobWorkItem item = new JobWorkItem.Builder().build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 2180a78..2b56ea8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -73,6 +73,7 @@
import android.util.DataUnit;
import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -124,6 +125,10 @@
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
+ // Used in JobStatus.
+ LocalServices.removeServiceForTest(JobSchedulerInternal.class);
+ LocalServices.addService(JobSchedulerInternal.class, mock(JobSchedulerInternal.class));
+
// Freeze the clocks at this moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 05780eb..1de7e37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -21,6 +21,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
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;
@@ -45,6 +46,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
@@ -582,6 +585,40 @@
}
@Test
+ public void testGetEffectiveStandbyBucket_buggyApp() {
+ when(mJobSchedulerInternal.isAppConsideredBuggy(
+ anyInt(), anyString(), anyInt(), anyString()))
+ .thenReturn(true);
+
+ final JobInfo jobInfo = new JobInfo.Builder(1234, TEST_JOB_COMPONENT).build();
+ JobStatus job = createJobStatus(jobInfo);
+
+ // Exempt apps be exempting.
+ job.setStandbyBucket(EXEMPTED_INDEX);
+ assertEquals(EXEMPTED_INDEX, job.getEffectiveStandbyBucket());
+
+ // Actual bucket is higher than the buggy cap, so the cap comes into effect.
+ job.setStandbyBucket(ACTIVE_INDEX);
+ assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
+
+ // Buckets at the cap or below shouldn't be affected.
+ job.setStandbyBucket(WORKING_INDEX);
+ assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(FREQUENT_INDEX);
+ assertEquals(FREQUENT_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(RARE_INDEX);
+ assertEquals(RARE_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(RESTRICTED_INDEX);
+ assertEquals(RESTRICTED_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(NEVER_INDEX);
+ assertEquals(NEVER_INDEX, job.getEffectiveStandbyBucket());
+ }
+
+ @Test
public void testModifyingInternalFlags() {
final JobInfo jobInfo =
new JobInfo.Builder(101, new ComponentName("foo", "bar"))
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index fb59ea2..7cc01e1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -58,6 +58,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.controllers.PrefetchController.PcConstants;
@@ -135,6 +136,9 @@
when(mJobSchedulerService.getPackagesForUidLocked(anyInt()))
.thenAnswer(invocationOnMock
-> mPackagesForUid.get(invocationOnMock.getArgument(0)));
+ // Used in JobStatus.
+ doReturn(mock(JobSchedulerInternal.class))
+ .when(() -> LocalServices.getService(JobSchedulerInternal.class));
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
// in the past, and PrefetchController sometimes floors values at 0, so if the test time
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 6f713e0..dce162c 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
@@ -85,6 +85,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
@@ -190,6 +191,8 @@
doReturn(mPowerAllowlistInternal)
.when(() -> LocalServices.getService(PowerAllowlistInternal.class));
// Used in JobStatus.
+ doReturn(mock(JobSchedulerInternal.class))
+ .when(() -> LocalServices.getService(JobSchedulerInternal.class));
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Used in QuotaController.Handler.
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index c6a5260..bc5e720 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -17,8 +17,10 @@
package com.android.server.wallpaper;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
+import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.MIN_SECONDARY_USER_ID;
import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -105,6 +107,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.List;
/**
* Tests for the {@link WallpaperManagerService} class.
@@ -261,6 +264,25 @@
}
/**
+ * Tests that the fundamental fields are set by the main WallpaperData constructor
+ */
+ @Test
+ public void testWallpaperDataConstructor() {
+ final int testUserId = MIN_SECONDARY_USER_ID;
+ for (int which: List.of(FLAG_LOCK, FLAG_SYSTEM)) {
+ WallpaperData newWallpaperData = new WallpaperData(testUserId, which);
+ assertEquals(which, newWallpaperData.mWhich);
+ assertEquals(testUserId, newWallpaperData.userId);
+
+ WallpaperData wallpaperData = mService.getWallpaperSafeLocked(testUserId, which);
+ assertEquals(wallpaperData.cropFile.getAbsolutePath(),
+ newWallpaperData.cropFile.getAbsolutePath());
+ assertEquals(wallpaperData.wallpaperFile.getAbsolutePath(),
+ newWallpaperData.wallpaperFile.getAbsolutePath());
+ }
+ }
+
+ /**
* Tests that internal basic data should be correct after boot up.
*/
@Test
@@ -402,10 +424,7 @@
fail("exception occurred while writing system wallpaper attributes");
}
- WallpaperData shouldMatchSystem = new WallpaperData(systemWallpaperData.userId,
- systemWallpaperData.wallpaperFile.getParentFile(),
- systemWallpaperData.wallpaperFile.getAbsolutePath(),
- systemWallpaperData.cropFile.getAbsolutePath());
+ WallpaperData shouldMatchSystem = new WallpaperData(0, FLAG_SYSTEM);
try {
TypedXmlPullParser parser = Xml.newBinaryPullParser();
mService.mWallpaperDataParser.parseWallpaperAttributes(parser, shouldMatchSystem, true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 662477d..8912591 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -26,6 +26,7 @@
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -48,6 +49,7 @@
import android.app.trust.ITrustManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
@@ -210,6 +212,40 @@
}
@Test
+ public void testOnErrorReceived_lockoutError() throws RemoteException {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+ setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+ session.goToInitialState();
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ session.onCookieReceived(
+ session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+ }
+ assertTrue(session.allCookiesReceived());
+ assertEquals(STATE_AUTH_STARTED, session.getState());
+
+ // Either of strong sensor's lockout should cancel both sensors.
+ final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
+ session.onErrorReceived(0, cookie1, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0);
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+
+ // If the sensor is STATE_CANCELING, delayed onAuthenticationRejected() shouldn't change the
+ // session state to STATE_AUTH_PAUSED.
+ session.onAuthenticationRejected(1);
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+ }
+
+ @Test
public void testCancelReducesAppetiteForCookies() throws Exception {
setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
mock(IBiometricAuthenticator.class));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index 6a45d5f..faece4f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -23,15 +23,19 @@
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.sensor.IVirtualSensorCallback;
+import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.hardware.Sensor;
import android.os.Binder;
import android.os.IBinder;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -39,12 +43,16 @@
import com.android.server.LocalServices;
import com.android.server.sensors.SensorManagerInternal;
+import com.google.common.collect.Iterables;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,14 +62,17 @@
private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
private static final int SENSOR_HANDLE = 7;
+ private static final int VIRTUAL_SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER;
+
@Mock
private SensorManagerInternal mSensorManagerInternalMock;
@Mock
private IVirtualSensorCallback mVirtualSensorCallback;
- private SensorController mSensorController;
+ @Mock
+ private IVirtualDevice mVirtualDevice;
+
private VirtualSensorEvent mSensorEvent;
private VirtualSensorConfig mVirtualSensorConfig;
- private IBinder mSensorToken;
@Before
public void setUp() throws Exception {
@@ -70,12 +81,10 @@
LocalServices.removeServiceForTest(SensorManagerInternal.class);
LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
- mSensorController = new SensorController(VIRTUAL_DEVICE_ID, mVirtualSensorCallback);
mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
mVirtualSensorConfig =
- new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+ new VirtualSensorConfig.Builder(VIRTUAL_SENSOR_TYPE, VIRTUAL_SENSOR_NAME)
.build();
- mSensorToken = new Binder("sensorToken");
}
@Test
@@ -86,62 +95,84 @@
Throwable thrown = assertThrows(
RuntimeException.class,
- () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+ () -> new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+ mVirtualSensorCallback, List.of(mVirtualSensorConfig)));
assertThat(thrown.getCause().getMessage())
.contains("Received an invalid virtual sensor handle");
}
@Test
- public void createSensor_success() {
- doCreateSensorSuccessfully();
+ public void createSensor_success() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
- assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+ assertThat(sensorController.getSensorDescriptors()).isNotEmpty();
}
@Test
- public void sendSensorEvent_invalidToken_throwsException() {
- doCreateSensorSuccessfully();
+ public void getSensorByHandle_success() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
+
+ VirtualSensor sensor = sensorController.getSensorByHandle(SENSOR_HANDLE);
+
+ assertThat(sensor).isNotNull();
+ assertThat(sensor.getHandle()).isEqualTo(SENSOR_HANDLE);
+ assertThat(sensor.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
+ assertThat(sensor.getType()).isEqualTo(VIRTUAL_SENSOR_TYPE);
+ }
+
+ @Test
+ public void getSensorByHandle_invalidHandle_returnsNull() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
+ final int invalidSensorHandle = 123456;
+
+ assertThat(sensorController.getSensorByHandle(invalidSensorHandle)).isNull();
+ }
+
+ @Test
+ public void sendSensorEvent_invalidToken_throwsException() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
assertThrows(
IllegalArgumentException.class,
- () -> mSensorController.sendSensorEvent(
+ () -> sensorController.sendSensorEvent(
new Binder("invalidSensorToken"), mSensorEvent));
}
@Test
- public void sendSensorEvent_success() {
- doCreateSensorSuccessfully();
+ public void sendSensorEvent_success() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
- mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+ clearInvocations(mSensorManagerInternalMock);
+ IBinder token = Iterables.getOnlyElement(sensorController.getSensorDescriptors().keySet());
+
+ sensorController.sendSensorEvent(token, mSensorEvent);
verify(mSensorManagerInternalMock).sendSensorEvent(
SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
mSensorEvent.getValues());
}
@Test
- public void unregisterSensor_invalidToken_throwsException() {
- doCreateSensorSuccessfully();
+ public void close_unregistersSensors() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
- assertThrows(
- IllegalArgumentException.class,
- () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
- }
-
- @Test
- public void unregisterSensor_success() {
- doCreateSensorSuccessfully();
-
- mSensorController.unregisterSensor(mSensorToken);
+ sensorController.close();
verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
- assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ assertThat(sensorController.getSensorDescriptors()).isEmpty();
}
- private void doCreateSensorSuccessfully() {
+ private SensorController doCreateSensorSuccessfully() throws RemoteException {
doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
anyInt(), anyInt(), anyString(), anyString(), anyFloat(), anyFloat(), anyFloat(),
anyInt(), anyInt(), anyInt(), any());
- assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig))
- .isEqualTo(SENSOR_HANDLE);
+ doReturn(VIRTUAL_DEVICE_ID).when(mVirtualDevice).getDeviceId();
+
+ SensorController sensorController = new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+ mVirtualSensorCallback, List.of(mVirtualSensorConfig));
+
+ List<VirtualSensor> sensors = sensorController.getSensorList();
+ assertThat(sensors).hasSize(1);
+ assertThat(sensors.get(0).getHandle()).isEqualTo(SENSOR_HANDLE);
+ return sensorController;
}
}
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 8884dba..379c8b7 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
@@ -359,7 +359,6 @@
mInputController = new InputController(mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
- mSensorController = new SensorController(VIRTUAL_DEVICE_ID_1, mVirtualSensorCallback);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
@@ -370,6 +369,7 @@
mLocalService = mVdms.getLocalServiceInstance();
mVdm = mVdms.new VirtualDeviceManagerImpl();
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1);
+ mSensorController = mDeviceImpl.getSensorControllerForTest();
}
@After
@@ -1722,7 +1722,7 @@
VirtualDeviceParams params) {
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
- mInputController, mSensorController, mCameraAccessController
+ mInputController, mCameraAccessController
/* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
index 2f57fd3..9135ef3 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -16,8 +16,6 @@
package com.android.server.contentprotection;
-import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -29,8 +27,8 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.UserHandle;
+import android.service.contentcapture.IContentProtectionService;
import android.view.contentcapture.ContentCaptureEvent;
-import android.view.contentcapture.IContentCaptureDirectManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -63,7 +61,7 @@
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+ @Mock private IContentProtectionService mMockContentProtectionService;
private RemoteContentProtectionService mRemoteContentProtectionService;
@@ -79,7 +77,7 @@
@Test
public void doesNotAutoConnect() {
assertThat(mConnectCallCount).isEqualTo(0);
- verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyZeroInteractions(mMockContentProtectionService);
}
@Test
@@ -98,8 +96,7 @@
mRemoteContentProtectionService.onLoginDetected(events);
- verify(mMockContentCaptureDirectManager)
- .sendEvents(events, FLUSH_REASON_LOGIN_DETECTED, /* options= */ null);
+ verify(mMockContentProtectionService).onLoginDetected(events);
}
private final class TestRemoteContentProtectionService extends RemoteContentProtectionService {
@@ -109,15 +106,15 @@
}
@Override // from ServiceConnector
- public synchronized AndroidFuture<IContentCaptureDirectManager> connect() {
+ public synchronized AndroidFuture<IContentProtectionService> connect() {
mConnectCallCount++;
- return AndroidFuture.completedFuture(mMockContentCaptureDirectManager);
+ return AndroidFuture.completedFuture(mMockContentProtectionService);
}
@Override // from ServiceConnector
- public boolean run(@NonNull ServiceConnector.VoidJob<IContentCaptureDirectManager> job) {
+ public boolean run(@NonNull ServiceConnector.VoidJob<IContentProtectionService> job) {
try {
- job.run(mMockContentCaptureDirectManager);
+ job.run(mMockContentProtectionService);
} catch (Exception ex) {
fail("Unexpected exception: " + ex);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 5837b21..708421d 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -52,6 +52,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -376,6 +377,116 @@
assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
}
+ @Test
+ public void testValidLuxThrottling() throws Exception {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+ mDisplayDeviceConfig.getLuxThrottlingData();
+ assertEquals(2, luxThrottlingData.size());
+
+ Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+ assertEquals(2, adaptiveOnBrightnessPoints.size());
+ assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+ assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
+
+ Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
+ assertEquals(2, adaptiveOffBrightnessPoints.size());
+ assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
+ assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
+ }
+
+ @Test
+ public void testInvalidLuxThrottling() throws Exception {
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
+
+ Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+ mDisplayDeviceConfig.getLuxThrottlingData();
+ assertEquals(1, luxThrottlingData.size());
+
+ Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+ assertEquals(1, adaptiveOnBrightnessPoints.size());
+ assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+ }
+
+ private String getValidLuxThrottling() {
+ return "<luxThrottling>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1000</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>5000</first>\n"
+ + " <second>0.5</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>default</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1500</first>\n"
+ + " <second>0.35</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>5500</first>\n"
+ + " <second>0.55</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + "</luxThrottling>";
+ }
+
+ private String getInvalidLuxThrottling() {
+ return "<luxThrottling>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1000</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>"
+ + " <point>" // second > hbm.transitionPoint, skipped
+ + " <first>1500</first>\n"
+ + " <second>0.9</second>\n"
+ + " </point>"
+ + " <point>" // same lux value, skipped
+ + " <first>1000</first>\n"
+ + " <second>0.5</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n" // Same type, skipped
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>2000</first>\n"
+ + " <second>0.35</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>6000</first>\n"
+ + " <second>0.55</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n" // Invalid points only, skipped
+ + " <type>default</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>2500</first>\n"
+ + " <second>0.99</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + "</luxThrottling>";
+ }
+
private String getRefreshThermalThrottlingMaps() {
return "<refreshRateThrottlingMap>\n"
+ " <refreshRateThrottlingPoint>\n"
@@ -405,6 +516,10 @@
}
private String getContent() {
+ return getContent(getValidLuxThrottling());
+ }
+
+ private String getContent(String brightnessCapConfig) {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<displayConfiguration>\n"
+ "<name>Example Display</name>"
@@ -462,6 +577,7 @@
+ "</point>\n"
+ "</sdrHdrRatioMap>\n"
+ "</highBrightnessMode>\n"
+ + brightnessCapConfig
+ "<screenOffBrightnessSensor>\n"
+ "<type>sensor_12345</type>\n"
+ "<name>Sensor 12345</name>\n"
@@ -731,8 +847,12 @@
}
private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent());
+ }
+
+ private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
Path tempFile = Files.createTempFile("display_config", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
mDisplayDeviceConfig.initFromFile(tempFile.toFile());
}
diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
new file mode 100644
index 0000000..c379d6b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class NormalBrightnessModeControllerTest {
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private final NormalBrightnessModeController mController = new NormalBrightnessModeController();
+
+ @Keep
+ private static Object[][] brightnessData() {
+ return new Object[][]{
+ // no brightness config
+ {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ // Auto brightness - on, config only for default
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - off, config only for default
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - off, config only for adaptive
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), PowerManager.BRIGHTNESS_MAX},
+ // Auto brightness - on, config only for adaptive
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - on, config for both
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), 0.4f},
+ // Auto brightness - off, config for both
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), 0.2f},
+ // Auto brightness - on, config for both, ambient high
+ {1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(1000f, 0.1f, 2000f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), PowerManager.BRIGHTNESS_MAX},
+ };
+ }
+
+ @Test
+ @Parameters(method = "brightnessData")
+ public void testReturnsCorrectMaxBrightness(float ambientLux, int autoBrightnessState,
+ Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig,
+ float expectedBrightness) {
+ setupController(ambientLux, autoBrightnessState, maxBrightnessConfig);
+
+ assertEquals(expectedBrightness, mController.getCurrentBrightnessMax(), FLOAT_TOLERANCE);
+ }
+
+ private void setupController(float ambientLux, int autoBrightnessState,
+ Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig) {
+ mController.onAmbientLuxChange(ambientLux);
+ mController.setAutoBrightnessState(autoBrightnessState);
+ mController.resetNbmData(maxBrightnessConfig);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
index 851d8f9..f05fa65 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -101,12 +101,6 @@
mMonitor.onEndDream();
super.onEndDream();
}
-
- @Override
- public void onWakeUp(@NonNull Runnable onCompleteCallback) {
- mMonitor.onWakeUp();
- super.onWakeUp(onCompleteCallback);
- }
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index e960e99..fe2ac17 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -254,6 +254,8 @@
.thenReturn(true);
when(res.getBoolean(eq(com.android.internal.R.bool.config_strongAuthRequiredOnBoot)))
.thenReturn(true);
+ when(res.getBoolean(eq(com.android.internal.R.bool.config_repairModeSupported)))
+ .thenReturn(true);
return res;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index 36dc6c5..a029db9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -84,6 +84,11 @@
}
@Override
+ File getRepairModePersistentDataFile() {
+ return remapToStorageDir(super.getRepairModePersistentDataFile());
+ }
+
+ @Override
PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
return mPersistentDataBlockManager;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 10ed882..23f14f8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -457,6 +457,31 @@
assertEquals(2, PersistentData.TYPE_SP_WEAVER);
}
+ @Test
+ public void testRepairMode_emptyPersistentData() {
+ assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+ }
+
+ @Test
+ public void testRepairMode_writeGatekeeperPersistentData() {
+ mStorage.writeRepairModePersistentData(
+ PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID, PAYLOAD);
+
+ final PersistentData data = mStorage.readRepairModePersistentData();
+ assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
+ assertArrayEquals(PAYLOAD, data.payload);
+ }
+
+ @Test
+ public void testRepairMode_writeWeaverPersistentData() {
+ mStorage.writeRepairModePersistentData(
+ PersistentData.TYPE_SP_WEAVER, SOME_USER_ID, PAYLOAD);
+
+ final PersistentData data = mStorage.readRepairModePersistentData();
+ assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
+ assertArrayEquals(PAYLOAD, data.payload);
+ }
+
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
new file mode 100644
index 0000000..70150c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.app.PropertyInvalidatedCache;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
+
+ @Before
+ public void setUp() throws Exception {
+ PropertyInvalidatedCache.disableForTestMode();
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void verifyPin_writeRepairModePW() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ }
+
+ @Test
+ public void verifyPattern_writeRepairModePW() {
+ mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ }
+
+ @Test
+ public void verifyPassword_writeRepairModePW() {
+ mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ }
+
+ @Test
+ public void verifyCredential_writeRepairModePW_repairModeActive() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+ setRepairModeActive(true);
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(
+ newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ }
+
+ @Test
+ public void deleteRepairModePersistentData() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+
+ mService.deleteRepairModePersistentDataIfNeeded();
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ }
+
+ @Test
+ public void verifyPin_userRepairMode() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ setRepairModeActive(true);
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+ .getResponseCode());
+ }
+
+ @Test
+ public void verifyPattern_userRepairMode() {
+ mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ setRepairModeActive(true);
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPattern("4321"), USER_REPAIR_MODE, 0 /* flags */)
+ .getResponseCode());
+ }
+
+ @Test
+ public void verifyPassword_userRepairMode() {
+ mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ setRepairModeActive(true);
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPassword("4321"), USER_REPAIR_MODE, 0 /* flags */)
+ .getResponseCode());
+ }
+
+ @Test
+ public void verifyCredential_userRepairMode_repairModeIsNotActive() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+ .getResponseCode());
+ }
+
+ @Test
+ public void verifyCredential_userRepairMode_wrongPin() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(
+ newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+ .getResponseCode());
+ setRepairModeActive(true);
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+ mService.getCredentialType(USER_REPAIR_MODE));
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(newPin("5678"), USER_REPAIR_MODE, 0 /* flags */)
+ .getResponseCode());
+ }
+
+ private void setRepairModeActive(boolean active) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);
+ }
+}
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 70cd0dc..c80ff45 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1066,9 +1066,13 @@
@SuppressWarnings("GuardedBy")
@Test
public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() {
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported))
+ .thenReturn(true);
when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
Settings.Secure.putInt(mContextSpy.getContentResolver(),
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index aa6ee09..0b13f9a 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -52,7 +52,7 @@
private static final int SOUND_TRIGGER_MODE = 0; // SOUND_TRIGGER_MODE_ALL_ENABLED
private static final int DEFAULT_SOUND_TRIGGER_MODE =
PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
- private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=true,"
+ private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=false,"
+ "advertise_is_enabled=true,"
+ "disable_animation=false,"
+ "enable_firewall=true,"
@@ -117,7 +117,7 @@
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
- testServiceDefaultValue_On(ServiceType.VIBRATION);
+ testServiceDefaultValue_Off(ServiceType.VIBRATION);
}
@SmallTest
@@ -211,7 +211,7 @@
private void verifyBatterySaverConstantsUpdated() {
final PowerSaveState vibrationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.VIBRATION);
- assertThat(vibrationState.batterySaverEnabled).isTrue();
+ assertThat(vibrationState.batterySaverEnabled).isFalse();
final PowerSaveState animationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.ANIMATION);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4debbb4..eaf4838 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -588,6 +588,8 @@
return wl;
});
mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false);
// apps allowed as convos
mService.setStringArrayResourceValue(PKG_O);
@@ -1931,8 +1933,24 @@
}
@Test
- public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception {
+ public void enqueueNotification_wakeLockSystemPropertyOff_noWakeLock() throws Exception {
mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ verifyZeroInteractions(mPowerManager);
+ }
+
+ @Test
+ public void enqueueNotification_wakeLockDeviceConfigOff_noWakeLock() throws Exception {
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "false", false);
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_setsWakeLockWorkSource", 0,
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 5863e9d..6a9f283 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -58,9 +58,20 @@
mPhoneWindowManager.overrideCanStartDreaming(true);
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertDreamRequest();
+ mPhoneWindowManager.overrideIsDreaming(true);
mPhoneWindowManager.assertLockedAfterAppTransitionFinished();
}
+ @Test
+ public void testAppTransitionFinishedCalledAfterDreamStoppedWillNotLockAgain() {
+ mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_DREAM_OR_SLEEP);
+ mPhoneWindowManager.overrideCanStartDreaming(true);
+ sendKey(KEYCODE_POWER);
+ mPhoneWindowManager.assertDreamRequest();
+ mPhoneWindowManager.overrideIsDreaming(false);
+ mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
+ }
+
/**
* Power double-press to launch camera does not lock device when the single press behavior is to
* dream.
@@ -72,7 +83,7 @@
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertCameraLaunch();
- mPhoneWindowManager.assertWillNotLockAfterAppTransitionFinished();
+ mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index af48cbd..766a88f 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -65,6 +65,7 @@
import android.media.AudioManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -344,6 +345,10 @@
doReturn(canDream).when(mDreamManagerInternal).canStartDreaming(anyBoolean());
}
+ void overrideIsDreaming(boolean isDreaming) {
+ doReturn(isDreaming).when(mDreamManagerInternal).isDreaming();
+ }
+
void overrideDisplayState(int state) {
doReturn(state).when(mDisplay).getState();
doReturn(state == STATE_ON).when(mDisplayPolicy).isAwake();
@@ -520,19 +525,26 @@
verify(mInputManagerInternal).toggleCapsLock(anyInt());
}
- void assertWillNotLockAfterAppTransitionFinished() {
- Assert.assertFalse(mPhoneWindowManager.mLockAfterAppTransitionFinished);
- }
-
void assertLockedAfterAppTransitionFinished() {
ArgumentCaptor<AppTransitionListener> transitionCaptor =
ArgumentCaptor.forClass(AppTransitionListener.class);
verify(mWindowManagerInternal).registerAppTransitionListener(
transitionCaptor.capture());
- transitionCaptor.getValue().onAppTransitionFinishedLocked(any());
+ final IBinder token = mock(IBinder.class);
+ transitionCaptor.getValue().onAppTransitionFinishedLocked(token);
verify(mPhoneWindowManager).lockNow(null);
}
+ void assertDidNotLockAfterAppTransitionFinished() {
+ ArgumentCaptor<AppTransitionListener> transitionCaptor =
+ ArgumentCaptor.forClass(AppTransitionListener.class);
+ verify(mWindowManagerInternal).registerAppTransitionListener(
+ transitionCaptor.capture());
+ final IBinder token = mock(IBinder.class);
+ transitionCaptor.getValue().onAppTransitionFinishedLocked(token);
+ verify(mPhoneWindowManager, never()).lockNow(null);
+ }
+
void assertGoToHomescreen() {
waitForIdle();
verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
diff --git a/tests/SilkFX/res/layout/gainmap_transform_test.xml b/tests/SilkFX/res/layout/gainmap_transform_test.xml
new file mode 100644
index 0000000..5aeb536
--- /dev/null
+++ b/tests/SilkFX/res/layout/gainmap_transform_test.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.test.silkfx.hdr.GainmapTransformsTest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/original"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Original" />
+
+ <Button
+ android:id="@+id/scaled"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Scaled (1/3)" />
+
+ <Button
+ android:id="@+id/rotate_90"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rotate 90" />
+
+ <Button
+ android:id="@+id/rotate_90_scaled"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rot90+Scale" />
+
+ <Button
+ android:id="@+id/crop"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Crop" />
+
+ <Button
+ android:id="@+id/crop_200"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Crop 200" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/source_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/sdr_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/sdr_source"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="8dp"
+ android:scaleType="fitStart" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/gainmap_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/gainmap"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="8dp"
+ android:scaleType="fitStart" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+</com.android.test.silkfx.hdr.GainmapTransformsTest>
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
index a6cdbb9..59a6078 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -55,7 +55,9 @@
Demo("Color Grid", R.layout.color_grid),
Demo("Gradient Sweep", R.layout.gradient_sweep),
Demo("Gainmap Image", R.layout.gainmap_image),
- Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false)
+ Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false),
+ Demo("Gainmap Transform Test", R.layout.gainmap_transform_test,
+ commonControls = false)
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
index a004fb5..585320ae 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
@@ -17,7 +17,12 @@
package com.android.test.silkfx.hdr
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Gainmap
import android.graphics.ImageDecoder
+import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.widget.Button
@@ -34,6 +39,25 @@
CropedSquaredScaled33
}
+fun gainmapVisualizer(gainmap: Gainmap): Bitmap {
+ val map = gainmap.gainmapContents
+ val gainmapVisualizer = Bitmap.createBitmap(map.width, map.height,
+ Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(gainmapVisualizer!!)
+ val paint = Paint()
+ paint.colorFilter = ColorMatrixColorFilter(
+ floatArrayOf(
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 0f, 255f
+ )
+ )
+ canvas.drawBitmap(map, 0f, 0f, paint)
+ canvas.setBitmap(null)
+ return gainmapVisualizer
+}
+
class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private fun decode(mode: DecodeMode) {
@@ -71,7 +95,7 @@
}
}
- val gainmapContents = gainmapImage.gainmap!!.gainmapContents!!
+ val gainmapContents = gainmapImage.gainmap?.let { gainmapVisualizer(it) }
val sdrBitmap = gainmapImage.also { it.gainmap = null }
findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap)
@@ -80,7 +104,7 @@
findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents)
findViewById<TextView>(R.id.gainmap_label)!!.text =
- "Gainmap Size: ${gainmapContents.width}x${gainmapContents.height}"
+ "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}"
}
override fun onFinishInflate() {
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt
new file mode 100644
index 0000000..20984fa
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.Matrix
+import android.util.AttributeSet
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.test.silkfx.R
+
+class GainmapTransformsTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
+
+ private val sourceImage = loadSample()
+
+ private fun loadSample(): Bitmap {
+ val source = ImageDecoder.createSource(resources.assets,
+ "gainmaps/${context.assets.list("gainmaps")!![0]}")
+
+ return ImageDecoder.decodeBitmap(source) { decoder, info, source ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ }
+ }
+
+ private fun process(transform: (Bitmap) -> Bitmap) {
+ val result = transform(sourceImage)
+
+ val gainmapContents = result.gainmap?.let { gainmapVisualizer(it) }
+ val sdrBitmap = result.also { it.gainmap = null }
+
+ findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap)
+ findViewById<TextView>(R.id.sdr_label)!!.text =
+ "SDR Size: ${sdrBitmap.width}x${sdrBitmap.height}"
+
+ findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents)
+ findViewById<TextView>(R.id.gainmap_label)!!.text =
+ "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}"
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val sourceInfo = findViewById<TextView>(R.id.source_info)!!
+ sourceInfo.text = "Original size ${sourceImage.width}x${sourceImage.height}"
+ process { it.copy(Bitmap.Config.ARGB_8888, false) }
+
+ findViewById<Button>(R.id.original)!!.setOnClickListener {
+ process { it.copy(Bitmap.Config.ARGB_8888, false) }
+ }
+
+ findViewById<Button>(R.id.scaled)!!.setOnClickListener {
+ process { Bitmap.createScaledBitmap(it, it.width / 3, it.height / 3, true) }
+ }
+
+ findViewById<Button>(R.id.rotate_90)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ Bitmap.createBitmap(it, 0, 0, width, height, m, false)
+ }
+ }
+
+ findViewById<Button>(R.id.rotate_90_scaled)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ m.preScale(.3f, .3f)
+ Bitmap.createBitmap(it, 0, 0, width, height, m, false)
+ }
+ }
+
+ findViewById<Button>(R.id.crop)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+ Bitmap.createBitmap(it, width / 2, height / 2,
+ width / 4, height / 4, null, false)
+ }
+ }
+
+ findViewById<Button>(R.id.crop_200)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(200.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ Bitmap.createBitmap(it, width / 2, height / 2,
+ width / 4, height / 4, m, false)
+ }
+ }
+ }
+}
\ No newline at end of file