Merge "Don't dismiss split during enter if already visible" into udc-dev
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/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/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/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/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/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 57935e3..70e924a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2933,62 +2933,21 @@
}
}
- if (!isComponentExist(context, cn)) {
- cn = null;
- }
-
- return cn;
- }
-
- /**
- * Return {@link ComponentName} of the CMF default wallpaper, or
- * {@link #getDefaultWallpaperComponent(Context)} if none is defined.
- *
- * @hide
- */
- public static ComponentName getCmfDefaultWallpaperComponent(Context context) {
- ComponentName cn = null;
- String[] cmfWallpaperMap = context.getResources().getStringArray(
- com.android.internal.R.array.cmf_default_wallpaper_component);
- if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
- Log.d(TAG, "No CMF wallpaper config");
- return getDefaultWallpaperComponent(context);
- }
-
- for (String entry : cmfWallpaperMap) {
- String[] cmfWallpaper;
- if (!TextUtils.isEmpty(entry)) {
- cmfWallpaper = entry.split(",");
- if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
- cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
- cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
- break;
- }
+ // Check if the package exists
+ if (cn != null) {
+ try {
+ final PackageManager packageManager = context.getPackageManager();
+ packageManager.getPackageInfo(cn.getPackageName(),
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ } catch (PackageManager.NameNotFoundException e) {
+ cn = null;
}
}
- if (!isComponentExist(context, cn)) {
- cn = null;
- }
-
return cn;
}
- private static boolean isComponentExist(Context context, ComponentName cn) {
- if (cn == null) {
- return false;
- }
- try {
- final PackageManager packageManager = context.getPackageManager();
- packageManager.getPackageInfo(cn.getPackageName(),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
/**
* Register a callback for lock wallpaper observation. Only the OS may use this.
*
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/app/admin/WifiSsidPolicy.java b/core/java/android/app/admin/WifiSsidPolicy.java
index 3fefe4b..ed53967 100644
--- a/core/java/android/app/admin/WifiSsidPolicy.java
+++ b/core/java/android/app/admin/WifiSsidPolicy.java
@@ -135,6 +135,10 @@
dest.writeArraySet(mSsids);
}
+ /**
+ * Two instances of WifiSsidPolicy is considered equal if they have
+ * the same WifiSsidPolicyType and the same set of WifiSsids
+ */
@Override
public boolean equals(Object thatObject) {
if (this == thatObject) {
@@ -147,6 +151,9 @@
return mPolicyType == that.mPolicyType && Objects.equals(mSsids, that.mSsids);
}
+ /**
+ * Returns the hash code value of WifiSsidPolicyType and WifiSsid set
+ */
@Override
public int hashCode() {
return Objects.hash(mPolicyType, mSsids);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d1063f6..d1c10fa 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -190,8 +190,7 @@
/**
* Wake lock flag: Turn the screen on when the wake lock is acquired.
* <p>
- * This flag requires {@link android.Manifest.permission#TURN_SCREEN_ON} for apps targeting
- * Android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and higher.
+ * This flag will require {@link android.Manifest.permission#TURN_SCREEN_ON} in future releases.
* </p><p>
* Normally wake locks don't actually wake the device, they just cause the screen to remain on
* once it's already on. This flag will cause the device to wake up when the wake lock is
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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1bbb7b4..a28be4b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5459,7 +5459,7 @@
}
private void updateRenderHdrSdrRatio() {
- mRenderHdrSdrRatio = mDisplay.getHdrSdrRatio();
+ mRenderHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mDisplay.getHdrSdrRatio());
mUpdateHdrSdrRatioInfo = true;
}
@@ -5487,22 +5487,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/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index ea75076..739c1bf 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2065,7 +2065,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);
}
}
@@ -2084,7 +2084,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);
}
}
@@ -2111,7 +2111,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);
}
}
@@ -2134,7 +2134,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);
}
}
@@ -2174,7 +2174,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);
}
}
@@ -2198,7 +2198,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);
}
}
@@ -2220,7 +2220,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);
}
}
@@ -2244,7 +2245,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);
}
}
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/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 8135f9c..dd52de4 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/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/config.xml b/core/res/res/values/config.xml
index 00f8db0d..ee8c0f8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1817,16 +1817,6 @@
specified -->
<string name="default_wallpaper_component" translatable="false">@null</string>
- <!-- CMF colors to default wallpaper component map, the component with color matching the device
- color will be the cmf default wallpapers. The default wallpaper will be default wallpaper
- component if not specified.
-
- E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper
- <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> -->
- <string-array name="cmf_default_wallpaper_component" translatable="false">
- <!-- Add packages here -->
- </string-array>
-
<!-- By default a product has no distinct default lock wallpaper -->
<item name="default_lock_wallpaper" type="drawable">@null</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a8518af..dc4eafd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2116,7 +2116,6 @@
<java-symbol type="string" name="data_usage_rapid_body" />
<java-symbol type="string" name="data_usage_rapid_app_body" />
<java-symbol type="string" name="default_wallpaper_component" />
- <java-symbol type="array" name="cmf_default_wallpaper_component" />
<java-symbol type="string" name="device_storage_monitor_notification_channel" />
<java-symbol type="string" name="dlg_ok" />
<java-symbol type="string" name="dump_heap_notification" />
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/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index 7571e44..d129891 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -24,6 +24,7 @@
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyStoreCryptoOperation;
+import android.system.keystore2.Authorization;
import libcore.util.EmptyArray;
@@ -119,6 +120,14 @@
mCipher = null;
}
+ private Authorization[] getKeyCharacteristics(Key key) {
+ if (!(key instanceof AndroidKeyStoreKey)) {
+ return new Authorization[] {};
+ }
+
+ return ((AndroidKeyStoreKey) key).getAuthorizations();
+ }
+
@Override
protected final void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException {
@@ -173,7 +182,7 @@
init(opmode, key, random);
initAlgorithmSpecificParameters();
try {
- ensureKeystoreOperationInitialized();
+ ensureKeystoreOperationInitialized(getKeyCharacteristics(key));
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidKeyException(e);
}
@@ -206,7 +215,7 @@
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
- ensureKeystoreOperationInitialized();
+ ensureKeystoreOperationInitialized(getKeyCharacteristics(key));
success = true;
} finally {
if (!success) {
@@ -236,7 +245,7 @@
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
- ensureKeystoreOperationInitialized();
+ ensureKeystoreOperationInitialized(getKeyCharacteristics(key));
success = true;
} finally {
if (!success) {
@@ -310,7 +319,8 @@
mCachedException = null;
}
- private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
+ private void ensureKeystoreOperationInitialized(Authorization[] keyCharacteristics)
+ throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
@@ -323,7 +333,7 @@
}
List<KeyParameter> parameters = new ArrayList<>();
- addAlgorithmSpecificParametersToBegin(parameters);
+ addAlgorithmSpecificParametersToBegin(parameters, keyCharacteristics);
int purpose;
if (mKeymasterPurposeOverride != -1) {
@@ -404,7 +414,7 @@
return null;
}
try {
- ensureKeystoreOperationInitialized();
+ ensureKeystoreOperationInitialized(getKeyCharacteristics(mKey));
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return null;
@@ -520,7 +530,7 @@
}
try {
- ensureKeystoreOperationInitialized();
+ ensureKeystoreOperationInitialized(getKeyCharacteristics(mKey));
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return;
@@ -597,7 +607,7 @@
}
try {
- ensureKeystoreOperationInitialized();
+ ensureKeystoreOperationInitialized(getKeyCharacteristics(mKey));
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
@@ -1012,6 +1022,22 @@
@NonNull List<KeyParameter> parameters);
/**
+ * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation,
+ * including the key characteristics. This is useful in case the parameters to {@code begin}
+ * depend on how the key was generated.
+ * The default implementation provided here simply ignores these key characteristics because
+ * they are not be needed for most engines.
+ *
+ * @param parameters keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ * @param keyCharacteristics The key's characteristics.
+ */
+ protected void addAlgorithmSpecificParametersToBegin(
+ @NonNull List<KeyParameter> parameters, Authorization[] keyCharacteristics) {
+ addAlgorithmSpecificParametersToBegin(parameters);
+ }
+
+ /**
* Invoked to obtain algorithm-specific parameters from the result of the KeyStore's
* {@code begin} operation.
*
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
index e9b66aa..3bb2564 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
@@ -288,16 +288,34 @@
}
}
+ private static boolean isMgfDigestTagPresentInKeyProperties(
+ Authorization[] keyCharacteristics) {
+ for (Authorization authorization : keyCharacteristics) {
+ if (authorization.keyParameter.tag == KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@Override
protected final void addAlgorithmSpecificParametersToBegin(
- @NonNull List<KeyParameter> parameters) {
- super.addAlgorithmSpecificParametersToBegin(parameters);
+ @NonNull List<KeyParameter> parameters, Authorization[] keyCharacteristics) {
+ super.addAlgorithmSpecificParametersToBegin(parameters, keyCharacteristics);
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
- parameters.add(KeyStore2ParameterUtils.makeEnum(
- KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest
- ));
+ // Only add the KM_TAG_RSA_OAEP_MGF_DIGEST tag to begin() if the MGF Digest is
+ // present in the key properties. Keys generated prior to Android 14 did not have
+ // this tag (Keystore didn't add it) so specifying any MGF digest tag would cause
+ // a begin() operation (on an Android 14 device) to fail (with a key that was generated
+ // on Android 13 or below).
+ if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics)) {
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest
+ ));
+ }
}
@Override
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..1959eb0 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
@@ -300,9 +300,12 @@
return mAllowSeamlessRotationDespiteNavBarMoving;
}
- /** @return whether the navigation bar will change sides during rotation. */
+ /**
+ * Returns {@code true} if the navigation bar will change sides during rotation and the display
+ * is not square.
+ */
public boolean navigationBarCanMove() {
- return mNavigationBarCanMove;
+ return mNavigationBarCanMove && mWidth != mHeight;
}
/** @return the rotation that would make the physical display "upside down". */
@@ -375,16 +378,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/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index ab8e7e6..f70d3ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -63,8 +63,10 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -104,6 +106,7 @@
private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
private final SplitWindowManager mSplitWindowManager;
+ private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final ImePositionProcessor mImePositionProcessor;
private final ResizingEffectPolicy mSurfaceEffectPolicy;
@@ -128,13 +131,14 @@
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
- DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer,
- int parallaxType) {
+ DisplayController displayController, DisplayImeController displayImeController,
+ ShellTaskOrganizer taskOrganizer, int parallaxType) {
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
+ mDisplayController = displayController;
mDisplayImeController = displayImeController;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
parentContainerCallbacks);
@@ -145,7 +149,7 @@
updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
@@ -314,7 +318,7 @@
mRotation = rotation;
mDensity = density;
mUiMode = uiMode;
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
@@ -324,7 +328,7 @@
/** Rotate the layout to specific rotation and calculate new bounds. The stable insets value
* should be calculated by display layout. */
- public void rotateTo(int newRotation, Rect stableInsets) {
+ public void rotateTo(int newRotation) {
final int rotationDelta = (newRotation - mRotation + 4) % 4;
final boolean changeOrient = (rotationDelta % 2) != 0;
@@ -337,7 +341,7 @@
// We only need new bounds here, other configuration should be update later.
mTempRect.set(mRootBounds);
mRootBounds.set(tmpRect);
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, stableInsets);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
initDividerPosition(mTempRect);
}
@@ -548,10 +552,9 @@
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
}
- private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds,
- @Nullable Rect stableInsets) {
+ private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
final boolean isLandscape = isLandscape(rootBounds);
- final Rect insets = stableInsets != null ? stableInsets : getDisplayInsets(context);
+ final Rect insets = getDisplayStableInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
// have difference for avoiding size-compat mode when switching unresizable apps in
@@ -634,7 +637,7 @@
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
- final Rect insets = getDisplayInsets(mContext);
+ final Rect insets = getDisplayStableInsets(mContext);
insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
@@ -705,13 +708,17 @@
return animator;
}
- private static Rect getDisplayInsets(Context context) {
- return context.getSystemService(WindowManager.class)
- .getMaximumWindowMetrics()
- .getWindowInsets()
- .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()
- | WindowInsets.Type.displayCutout())
- .toRect();
+ private Rect getDisplayStableInsets(Context context) {
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(context.getDisplayId());
+ return displayLayout != null
+ ? displayLayout.stableInsets()
+ : context.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics()
+ .getWindowInsets()
+ .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()
+ | WindowInsets.Type.displayCutout())
+ .toRect();
}
private static boolean isLandscape(Rect bounds) {
@@ -784,7 +791,7 @@
private int getSmallestWidthDp(Rect bounds) {
mTempRect.set(bounds);
- mTempRect.inset(getDisplayInsets(mContext));
+ mTempRect.inset(getDisplayStableInsets(mContext));
final int minWidth = Math.min(mTempRect.width(), mTempRect.height());
final float density = mContext.getResources().getDisplayMetrics().density;
return (int) (minWidth / density);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 18898f1..07d11cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -534,17 +534,6 @@
return;
}
- if (ENABLE_SHELL_TRANSITIONS) {
- if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
- mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
- isPipTopLeft()
- ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
- mPipTransitionController.startExitTransition(
- TRANSIT_EXIT_PIP_TO_SPLIT, wct, null /* destinationBounds */);
- return;
- }
- }
-
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
final Rect destinationBounds = new Rect(displayBounds);
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
@@ -553,10 +542,8 @@
// For exiting to fullscreen, the windowing mode of task will be changed to fullscreen
// until the animation is finished. Otherwise if the activity is resumed and focused at the
// begin of aniamtion, the app may do something too early to distub the animation.
- final boolean toFullscreen = destinationBounds.equals(displayBounds);
- if (Transitions.SHELL_TRANSITIONS_ROTATION || (Transitions.ENABLE_SHELL_TRANSITIONS
- && !toFullscreen)) {
+ if (Transitions.SHELL_TRANSITIONS_ROTATION) {
// When exit to fullscreen with Shell transition enabled, we update the Task windowing
// mode directly so that it can also trigger display rotation and visibility update in
// the same transition if there will be any.
@@ -588,9 +575,29 @@
mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
+ wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
+ isPipToTopLeft()
+ ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ mPipTransitionController.startExitTransition(
+ TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds);
+ return;
+ }
+
+ if (mSplitScreenOptional.isPresent()) {
+ // If pip activity will reparent to origin task case and if the origin task still
+ // under split root, apply exit split transaction to make it expand to fullscreen.
+ SplitScreenController split = mSplitScreenOptional.get();
+ if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+ split.prepareExitSplitScreen(wct, split.getStageOfTask(
+ mTaskInfo.lastParentTaskIdBeforePip));
+ }
+ }
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
+
if (mSplitScreenOptional.isPresent()) {
// If pip activity will reparent to origin task case and if the origin task still under
// split root, just exit split screen here to ensure it could expand to fullscreen.
@@ -1666,17 +1673,6 @@
}
}
- private boolean isPipTopLeft() {
- if (!mSplitScreenOptional.isPresent()) {
- return false;
- }
- final Rect topLeft = new Rect();
- final Rect bottomRight = new Rect();
- mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-
- return topLeft.contains(mPipBoundsState.getBounds());
- }
-
private boolean isPipToTopLeft() {
if (!mSplitScreenOptional.isPresent()) {
return false;
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 5086e2c..86b0f33 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,
@@ -981,12 +989,7 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull TaskInfo taskInfo) {
- final int changeSize = info.getChanges().size();
- if (changeSize < 4) {
- throw new RuntimeException(
- "Got an exit-pip-to-split transition with unexpected change-list");
- }
- for (int i = changeSize - 1; i >= 0; i--) {
+ for (int i = info.getChanges().size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = info.getChanges().get(i);
final int mode = change.getMode();
@@ -1015,24 +1018,17 @@
private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
@NonNull SurfaceControl.Transaction startTransaction) {
final SurfaceControl leash = prevPipTaskChange.getLeash();
- final Rect bounds = prevPipTaskChange.getEndAbsBounds();
- final Point offset = prevPipTaskChange.getEndRelOffset();
- bounds.offset(-offset.x, -offset.y);
+ startTransaction.remove(leash);
- startTransaction.setWindowCrop(leash, null);
- startTransaction.setMatrix(leash, 1, 0, 0, 1);
- startTransaction.setCornerRadius(leash, 0);
- startTransaction.setPosition(leash, bounds.left, bounds.top);
-
- if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) {
- if (mPipAnimationController.getCurrentAnimator() != null) {
- mPipAnimationController.getCurrentAnimator().cancel();
- }
- startTransaction.setAlpha(leash, 1);
- }
mHasFadeOut = false;
mCurrentPipTaskToken = null;
- mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo());
+
+ // clean-up the state in PipTaskOrganizer if the PipTaskOrganizer#onTaskAppeared() hasn't
+ // been called yet with its leash reference now pointing to a new SurfaceControl not
+ // matching the leash of the pip we are removing.
+ if (mPipOrganizer.getSurfaceControl() == leash) {
+ mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo());
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 2fff0e4..e1bcd70c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,6 +38,7 @@
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -223,6 +224,13 @@
return false;
}
+ /** Whether a particular package is same as current pip package. */
+ public boolean isInPipPackage(String packageName) {
+ final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
+ return packageName != null && inPipTask != null
+ && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+ }
+
/** Add PiP-related changes to `outWCT` for the given request. */
public void augmentRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
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/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index ea33a1f..e7a367f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -88,6 +88,7 @@
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -332,6 +333,11 @@
return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
}
+ /** Get the split stage of task is under it. */
+ public @StageType int getStageOfTask(int taskId) {
+ return mStageCoordinator.getStageOfTask(taskId);
+ }
+
/** Check split is foreground and task is under split or not by taskId. */
public boolean isTaskInSplitScreenForeground(int taskId) {
return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
@@ -378,17 +384,35 @@
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
- public void enterSplitScreen(int taskId, boolean leftOrTop) {
- enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
- }
-
+ /**
+ * Doing necessary window transaction for other transition handler need to enter split in
+ * transition.
+ */
public void prepareEnterSplitScreen(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo taskInfo, int startPosition) {
- mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition);
+ mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition,
+ false /* resizeAnim */);
}
- public void finishEnterSplitScreen(SurfaceControl.Transaction t) {
- mStageCoordinator.finishEnterSplitScreen(t);
+ /**
+ * Doing necessary surface transaction for other transition handler need to enter split in
+ * transition when finished.
+ */
+ public void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+ mStageCoordinator.finishEnterSplitScreen(finishT);
+ }
+
+ /**
+ * Doing necessary window transaction for other transition handler need to exit split in
+ * transition.
+ */
+ public void prepareExitSplitScreen(WindowContainerTransaction wct,
+ @StageType int stageToTop) {
+ mStageCoordinator.prepareExitSplitScreen(stageToTop, wct);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
}
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
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 85b71fe..79002bb 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
@@ -122,7 +122,6 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -171,7 +170,6 @@
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private final DisplayLayout mDisplayLayout;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -311,7 +309,6 @@
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
this::onTransitionAnimationComplete, this);
mDisplayController.addDisplayWindowListener(this);
- mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
@@ -345,7 +342,6 @@
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
mDisplayController.addDisplayWindowListener(this);
- mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
@@ -393,7 +389,7 @@
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
WindowContainerTransaction wct) {
- prepareEnterSplitScreen(wct, task, stagePosition);
+ prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
if (ENABLE_SHELL_TRANSITIONS) {
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
null, this,
@@ -491,20 +487,26 @@
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ mSplitRequest = new SplitRequest(intent.getIntent(), position);
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
-
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
+ // If this should be mixed, just send the intent to avoid split handle transition directly.
+ if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
+ mTaskOrganizer.applyTransaction(wct);
+ return;
+ }
+
// If split screen is not activated, we're expecting to open a pair of apps to split.
final int extraTransitType = mMainStage.isActive()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
- prepareEnterSplitScreen(wct, null /* taskInfo */, position);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
extraTransitType, !mIsDropEntering);
@@ -561,7 +563,6 @@
if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
- mSplitRequest = new SplitRequest(intent.getIntent(), position);
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -1449,7 +1450,7 @@
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
* to be used when exiting split might be bundled with other window operations.
*/
- private void prepareExitSplitScreen(@StageType int stageToTop,
+ void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
if (!mMainStage.isActive()) return;
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
@@ -1457,7 +1458,8 @@
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
- prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED,
+ !mIsDropEntering);
}
/**
@@ -1465,17 +1467,19 @@
* into side stage.
*/
void prepareEnterSplitScreen(WindowContainerTransaction wct,
- @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
+ boolean resizeAnim) {
onSplitScreenEnter();
if (isSplitActive()) {
- prepareBringSplit(wct, taskInfo, startPosition);
+ prepareBringSplit(wct, taskInfo, startPosition, resizeAnim);
} else {
- prepareActiveSplit(wct, taskInfo, startPosition);
+ prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim);
}
}
private void prepareBringSplit(WindowContainerTransaction wct,
- @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
+ boolean resizeAnim) {
if (taskInfo != null) {
wct.startTask(taskInfo.taskId,
resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1487,12 +1491,13 @@
// won't guarantee to put the task to the indicated new position.
mMainStage.evictAllChildren(wct);
mMainStage.reparentTopTask(wct);
- prepareSplitLayout(wct);
+ prepareSplitLayout(wct, resizeAnim);
}
}
private void prepareActiveSplit(WindowContainerTransaction wct,
- @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
+ boolean resizeAnim) {
if (!ENABLE_SHELL_TRANSITIONS) {
// Legacy transition we need to create divider here, shell transition case we will
// create it on #finishEnterSplitScreen
@@ -1507,17 +1512,17 @@
mSideStage.addTask(taskInfo, wct);
}
mMainStage.activate(wct, true /* includingTopTask */);
- prepareSplitLayout(wct);
+ prepareSplitLayout(wct, resizeAnim);
}
- private void prepareSplitLayout(WindowContainerTransaction wct) {
- if (mIsDropEntering) {
- mSplitLayout.resetDividerPosition();
- } else {
+ private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) {
+ if (resizeAnim) {
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else {
+ mSplitLayout.resetDividerPosition();
}
updateWindowBounds(mSplitLayout, wct);
- if (!mIsDropEntering) {
+ if (resizeAnim) {
// Reset its smallest width dp to avoid is change layout before it actually resized to
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
@@ -1527,21 +1532,22 @@
setRootForceTranslucent(false, wct);
}
- void finishEnterSplitScreen(SurfaceControl.Transaction t) {
- mSplitLayout.update(t);
+ void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+ mSplitLayout.update(finishT);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
getSideStageBounds());
- setDividerVisibility(true, t);
+ setDividerVisibility(true, finishT);
// Ensure divider surface are re-parented back into the hierarchy at the end of the
// transition. See Transition#buildFinishTransaction for more detail.
- t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+ finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- t.show(mRootTaskLeash);
+ updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
+ finishT.show(mRootTaskLeash);
setSplitsVisible(true);
mIsDropEntering = false;
+ mSplitRequest = null;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
@@ -1634,7 +1640,7 @@
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
- if (present && visible) {
+ if (present) {
updateRecentTasksSplitPair();
}
@@ -1693,7 +1699,7 @@
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
- mDisplayImeController, mTaskOrganizer,
+ mDisplayController, mDisplayImeController, mTaskOrganizer,
PARALLAX_ALIGN_CENTER /* parallaxType */);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
@@ -2157,8 +2163,6 @@
if (displayId != DEFAULT_DISPLAY) {
return;
}
- mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
-
if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi)
&& mMainStage.isActive()
&& mSplitLayout.updateConfiguration(newConfig)
@@ -2175,10 +2179,9 @@
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
- if (!mMainStage.isActive()) return;
+ if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return;
- mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
- mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets());
+ mSplitLayout.rotateTo(toRotation);
if (newDisplayAreaInfo != null) {
mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
}
@@ -2504,6 +2507,7 @@
if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
startTransaction, finishTransaction, finishCallback)) {
mSplitLayout.update(startTransaction);
+ startTransaction.apply();
return true;
}
}
@@ -2888,6 +2892,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. */
@@ -2914,7 +2924,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 92ff5fe..a01eddb 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
@@ -127,7 +127,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;
}
@@ -183,12 +184,13 @@
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
mChildrenTaskInfo.put(taskId, taskInfo);
- updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
- 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;
}
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
mCallbacks.onChildTaskAppeared(taskId);
sendStatusChanged();
} else {
@@ -223,7 +225,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 */);
@@ -417,7 +419,7 @@
}
t.setCrop(leash, null);
t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
- if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ if (firstAppeared) {
t.setAlpha(leash, 1f);
t.setMatrix(leash, 1, 0, 0, 1);
t.show(leash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index ae72220..4cfbbd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -20,6 +20,7 @@
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -370,8 +371,11 @@
mStartingWindowRecordManager.addRecord(taskId, tView);
}
- private void removeWindowInner(View decorView, boolean hideView) {
+ private void removeWindowInner(@NonNull View decorView, boolean hideView) {
requestTopUi(false);
+ if (!decorView.isAttachedToWindow()) {
+ return;
+ }
if (hideView) {
decorView.setVisibility(View.GONE);
}
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 2e7fca3..5baf2e3 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
@@ -139,7 +139,8 @@
* `taskView`.
* @param taskView the pending transition should be for this.
*/
- private PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) {
+ @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)) {
@@ -398,10 +399,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/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 3ec433e..2aa9f20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -24,12 +24,14 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.os.IBinder;
import android.util.Log;
import android.util.Pair;
@@ -41,6 +43,7 @@
import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
@@ -158,7 +161,9 @@
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) {
+ if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()
+ && request.getTriggerTask() != null && mSplitHandler.getSplitItemPosition(
+ request.getTriggerTask().token) != SPLIT_POSITION_UNDEFINED) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
+ "Split-Screen is active, so treat it as Mixed.");
if (request.getRemoteTransition() != null) {
@@ -590,6 +595,17 @@
return true;
}
+ /** Use to when split use intent to enter, check if this enter transition should be mixed or
+ * not.*/
+ public boolean shouldSplitEnterMixed(PendingIntent intent) {
+ // Check if this intent package is same as pip one or not, if true we want let the pip
+ // task enter split.
+ if (mPipHandler != null) {
+ return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
+ }
+ return false;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 3bf278c..e52fd00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -260,6 +260,12 @@
// This is the only way to get display-id currently, so check display capabilities here.
final DisplayLayout displayLayout = displayController.getDisplayLayout(
topTaskInfo.displayId);
+ // This condition should be true when using gesture navigation or the screen size is large
+ // (>600dp) because the bar is small relative to screen.
+ if (displayLayout.allowSeamlessRotationDespiteNavBarMoving()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " nav bar allows seamless.");
+ return ROTATION_ANIMATION_SEAMLESS;
+ }
// For the upside down rotation we don't rotate seamlessly as the navigation bar moves
// position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
// will not enter the reverse portrait orientation, so actually the orientation won't
@@ -272,13 +278,9 @@
return animationHint;
}
- // If the navigation bar can't change sides, then it will jump when we change orientations
- // and we don't rotate seamlessly - unless that is allowed, e.g. with gesture navigation
- // where the navbar is low-profile enough that this isn't very noticeable.
- if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
- && (!(displayLayout.navigationBarCanMove()
- && (displayChange.getStartAbsBounds().width()
- != displayChange.getStartAbsBounds().height())))) {
+ // If the navigation bar cannot change sides, then it will jump when changing orientation
+ // so do not use seamless rotation.
+ if (!displayLayout.navigationBarCanMove()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" nav bar changes sides, so not seamless.");
return animationHint;
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..31ca16c 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
@@ -809,6 +809,9 @@
track.mReadyTransitions.remove(0);
track.mActiveTransition = ready;
if (ready.mAborted) {
+ if (ready.mStartT != null) {
+ ready.mStartT.apply();
+ }
// finish now since there's nothing to animate. Calls back into processReadyQueue
onFinish(ready, null, null);
return;
@@ -936,10 +939,6 @@
/** Aborts a transition. This will still queue it up to maintain order. */
private void onAbort(ActiveTransition transition) {
final Track track = mTracks.get(transition.getTrack());
- // apply immediately since they may be "parallel" operations: We currently we use abort for
- // thing which are independent to other transitions (like starting-window transfer).
- transition.mStartT.apply();
- transition.mFinishT.apply();
transition.mAborted = true;
mTracer.logAborted(transition.mInfo.getDebugId());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index c33a633..936faa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -170,6 +170,9 @@
if (isOpeningType(mode)) {
t.setAlpha(leash, 0.f);
}
+ // Set the transition leash position to 0 in case the divider leash position being
+ // taking down.
+ t.setPosition(leash, 0, 0);
t.setLayer(leash, Integer.MAX_VALUE);
return;
}
@@ -228,7 +231,11 @@
t.reparent(change.getLeash(), leashSurface);
t.setAlpha(change.getLeash(), 1.0f);
t.show(change.getLeash());
- t.setPosition(change.getLeash(), 0, 0);
+ if (!isDividerBar(change)) {
+ // For divider, don't modify its inner leash position when creating the outer leash
+ // for the transition. In case the position being wrong after the transition finished.
+ t.setPosition(change.getLeash(), 0, 0);
+ }
t.setLayer(change.getLeash(), 0);
return leashSurface;
}
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/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 3d77948..443cea2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
@@ -41,6 +42,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
@@ -57,6 +59,7 @@
public class SplitLayoutTests extends ShellTestCase {
@Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
@Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
+ @Mock DisplayController mDisplayController;
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
@@ -72,6 +75,7 @@
getConfiguration(),
mSplitLayoutHandler,
mCallbacks,
+ mDisplayController,
mDisplayImeController,
mTaskOrganizer,
SplitLayout.PARALLAX_NONE));
@@ -100,6 +104,10 @@
// Verify updateConfiguration returns true if the density changed.
config.densityDpi = 123;
assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration checks the current DisplayLayout
+ verify(mDisplayController, times(5)) // init * 1 + updateConfiguration * 4
+ .getDisplayLayout(anyInt());
}
@Test
@@ -168,6 +176,14 @@
verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt());
}
+ @Test
+ public void testRoateTo_checksDisplayLayout() {
+ mSplitLayout.rotateTo(90);
+
+ verify(mDisplayController, times(2)) // init * 1 + rotateTo * 1
+ .getDisplayLayout(anyInt());
+ }
+
private void waitDividerFlingFinished() {
verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
mRunnableCaptor.capture());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 66b6c62..2dcdc74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -159,7 +159,7 @@
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
verify(mMainStage).reparentTopTask(eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
@@ -177,7 +177,7 @@
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
}
@@ -189,7 +189,7 @@
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
}
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 71ad0d7..03ed18c 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,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
@SmallTest
@@ -295,4 +300,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..ff380e9 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;
@@ -703,8 +702,8 @@
createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
final DisplayController displays = createTestDisplayController();
- final @Surface.Rotation int upsideDown = displays
- .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
+ final DisplayLayout displayLayout = displays.getDisplayLayout(DEFAULT_DISPLAY);
+ final @Surface.Rotation int upsideDown = displayLayout.getUpsideDownRotation();
TransitionInfo.Change displayChange = new ChangeBuilder(TRANSIT_CHANGE)
.setFlags(FLAG_IS_DISPLAY).setRotate().build();
@@ -744,7 +743,8 @@
assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
displayChange, noTask, displays));
- // Not seamless if one of rotations is upside-down
+ // Not seamless if the nav bar cares rotation and one of rotations is upside-down.
+ doReturn(false).when(displayLayout).allowSeamlessRotationDespiteNavBarMoving();
displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
.setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build();
final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
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/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/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 38bb447..b7c97208 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -4933,8 +4933,12 @@
* Called when an output frame has rendered on the output surface.
* <p>
* <strong>Note:</strong> This callback is for informational purposes only: to get precise
- * render timing samples, and can be significantly delayed and batched. Some frames may have
- * been rendered even if there was no callback generated.
+ * render timing samples, and can be significantly delayed and batched. Starting with
+ * Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, a callback will always
+ * be received for each rendered frame providing the MediaCodec is still in the executing
+ * state when the callback is dispatched. Prior to Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, some frames may have been
+ * rendered even if there was no callback generated.
*
* @param codec the MediaCodec instance
* @param presentationTimeUs the presentation time (media time) of the frame rendered.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 3fcb7f3..ffc0479 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -116,10 +116,7 @@
&& !TextUtils.isEmpty(mPackageName)) {
RouteListingPreference routeListingPreference =
mRouterManager.getRouteListingPreference(mPackageName);
- if (routeListingPreference != null) {
- Api34Impl.onRouteListingPreferenceUpdated(null, routeListingPreference,
- mPreferenceItemMap);
- }
+ Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
}
refreshDevices();
}
@@ -631,7 +628,7 @@
}
/**
- * Ignore callback here since we'll also receive {@link onRequestFailed} with reason code.
+ * Ignore callback here since we'll also receive {@link #onRequestFailed} with reason code.
*/
@Override
public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
@@ -656,9 +653,9 @@
public void onRouteListingPreferenceUpdated(
String packageName,
RouteListingPreference routeListingPreference) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
- mPreferenceItemMap);
+ if (TextUtils.equals(mPackageName, packageName)) {
+ Api34Impl.onRouteListingPreferenceUpdated(
+ routeListingPreference, mPreferenceItemMap);
refreshDevices();
}
}
@@ -746,7 +743,6 @@
@DoNotInline
static void onRouteListingPreferenceUpdated(
- String packageName,
RouteListingPreference routeListingPreference,
Map<String, RouteListingPreference.Item> preferenceItemMap) {
preferenceItemMap.clear();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index aa5f3df..39780f3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -73,6 +73,7 @@
public class InfoMediaManagerTest {
private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_PACKAGE_NAME_2 = "com.test.packagename2";
private static final String TEST_ID = "test_id";
private static final String TEST_ID_1 = "test_id_1";
private static final String TEST_ID_2 = "test_id_2";
@@ -308,7 +309,54 @@
}
@Test
- public void onRouteChanged_getAvailableRoutesWithPrefernceListExit_ordersRoutes() {
+ public void onRouteChanged_getAvailableRoutesWithPreferenceListExit_ordersRoutes() {
+ RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME);
+ setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo);
+
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+ when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+ setAvailableRoutesList(TEST_PACKAGE_NAME);
+
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
+ routeListingPreference);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
+ assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+ assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
+ assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
+ assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
+ }
+
+ @Test
+ public void onRouteChanged_preferenceListUpdateWithDifferentPkg_notOrdersRoutes() {
+ RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME_2);
+ setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo);
+
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+ when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+ setAvailableRoutesList(TEST_PACKAGE_NAME);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME_2,
+ routeListingPreference);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(1);
+ assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+ }
+
+ private RouteListingPreference setUpPreferenceList(String packageName) {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
@@ -324,57 +372,40 @@
RouteListingPreference routeListingPreference =
new RouteListingPreference.Builder().setItems(
preferenceItemList).setUseSystemOrdering(false).build();
- when(mRouterManager.getRouteListingPreference(TEST_PACKAGE_NAME))
+ when(mRouterManager.getRouteListingPreference(packageName))
.thenReturn(routeListingPreference);
+ return routeListingPreference;
+ }
+ private void setUpSelectedRoutes(String packageName) {
final List<MediaRoute2Info> selectedRoutes = new ArrayList<>();
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(info.getClientPackageName()).thenReturn(packageName);
when(info.isSystemRoute()).thenReturn(true);
selectedRoutes.add(info);
when(mRouterManager.getSelectedRoutes(any())).thenReturn(selectedRoutes);
-
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo);
-
- when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
- when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
-
- setAvailableRoutesList();
-
- mInfoMediaManager.mRouterManager = mRouterManager;
- mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
- routeListingPreference);
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
- assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
- assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
- assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
}
- private List<MediaRoute2Info> setAvailableRoutesList() {
+ private List<MediaRoute2Info> setAvailableRoutesList(String packageName) {
final List<MediaRoute2Info> availableRoutes = new ArrayList<>();
final MediaRoute2Info availableInfo1 = mock(MediaRoute2Info.class);
when(availableInfo1.getId()).thenReturn(TEST_ID_2);
- when(availableInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo1.getClientPackageName()).thenReturn(packageName);
when(availableInfo1.getType()).thenReturn(TYPE_REMOTE_TV);
availableRoutes.add(availableInfo1);
final MediaRoute2Info availableInfo2 = mock(MediaRoute2Info.class);
when(availableInfo2.getId()).thenReturn(TEST_ID_3);
- when(availableInfo2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo2.getClientPackageName()).thenReturn(packageName);
availableRoutes.add(availableInfo2);
final MediaRoute2Info availableInfo3 = mock(MediaRoute2Info.class);
when(availableInfo3.getId()).thenReturn(TEST_ID_4);
- when(availableInfo3.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo3.getClientPackageName()).thenReturn(packageName);
availableRoutes.add(availableInfo3);
- when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(
+ when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(
availableRoutes);
return availableRoutes;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0a9a184..0aa121d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -996,7 +996,6 @@
android:name=".notetask.shortcut.LaunchNoteTaskActivity"
android:exported="true"
android:excludeFromRecents="true"
- android:resizeableActivity="false"
android:theme="@android:style/Theme.NoDisplay" >
<intent-filter>
@@ -1012,7 +1011,6 @@
android:exported="false"
android:enabled="true"
android:excludeFromRecents="true"
- android:resizeableActivity="false"
android:theme="@android:style/Theme.NoDisplay" />
<activity
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/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 3fc0965..fc9c917 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -43,7 +43,7 @@
<!-- Not quite optimal but needed to translate these items as a group. The
NotificationIconContainer has its own logic for translation. -->
- <LinearLayout
+ <com.android.keyguard.KeyguardStatusAreaView
android:id="@+id/keyguard_status_area"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -63,5 +63,5 @@
android:paddingStart="@dimen/below_clock_padding_start_icons"
android:visibility="invisible"
/>
- </LinearLayout>
+ </com.android.keyguard.KeyguardStatusAreaView>
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml
index 2a80920..3d1cb5e 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml
@@ -16,5 +16,5 @@
-->
<resources>
<!-- Invisibility to use for the date & weather view when it is disabled by a clock -->
- <integer name="keyguard_date_weather_view_invisibility">8</integer>
+ <integer name="keyguard_date_weather_view_invisibility">4</integer>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 4b79689..39dd90e 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -148,4 +148,9 @@
<dimen name="default_dot_diameter">34dp</dimen>
<dimen name="default_dot_spacing">0dp</dimen>
+ <!-- Weather clock smartspace scaling to apply for the weather clock -->
+ <item name="weather_clock_smartspace_scale" type="dimen" format="float">1.0</item>
+ <dimen name="weather_clock_smartspace_translateX">0dp</dimen>
+ <dimen name="weather_clock_smartspace_translateY">0dp</dimen>
+
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/ids.xml b/packages/SystemUI/res-keyguard/values/ids.xml
index 0dff4ff..1435907 100644
--- a/packages/SystemUI/res-keyguard/values/ids.xml
+++ b/packages/SystemUI/res-keyguard/values/ids.xml
@@ -17,4 +17,18 @@
<resources>
<item type="id" name="header_footer_views_added_tag_key" />
+
+ <!-- animation channels for keyguard status area -->
+ <item type="id" name="translate_x_clock_design_animator_tag" />
+ <item type="id" name="translate_x_clock_design_animator_start_tag" />
+ <item type="id" name="translate_x_clock_design_animator_end_tag" />
+ <item type="id" name="translate_x_aod_animator_tag" />
+ <item type="id" name="translate_x_aod_animator_start_tag" />
+ <item type="id" name="translate_x_aod_animator_end_tag" />
+ <item type="id" name="translate_y_clock_size_animator_tag" />
+ <item type="id" name="translate_y_clock_size_animator_start_tag" />
+ <item type="id" name="translate_y_clock_size_animator_end_tag" />
+ <item type="id" name="translate_y_clock_design_animator_tag" />
+ <item type="id" name="translate_y_clock_design_animator_start_tag" />
+ <item type="id" name="translate_y_clock_design_animator_end_tag" />
</resources>
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
index a595566..7105721 100644
--- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -110,7 +110,9 @@
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:ellipsize="end"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:ellipsize="marquee"
+ android:singleLine="true"
android:maxLines="1"
android:textColor="@color/media_dialog_item_main_content"
android:textSize="14sp"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
index c5979cc..7a8c82c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -65,8 +65,8 @@
} else {
1
}
- viewsToTranslate.forEach { (view, direction) ->
- view.get()?.translationX = xTrans * direction.multiplier * rtlMultiplier
+ viewsToTranslate.forEach { (view, direction, func) ->
+ view.get()?.let { func(it, xTrans * direction.multiplier * rtlMultiplier) }
}
}
@@ -77,7 +77,7 @@
.filter { it.shouldBeAnimated() }
.mapNotNull {
parent.findViewById<View>(it.viewId)?.let { view ->
- ViewToTranslate(WeakReference(view), it.direction)
+ ViewToTranslate(WeakReference(view), it.direction, it.translateFunc)
}
}
.toList()
@@ -91,14 +91,19 @@
data class ViewIdToTranslate(
val viewId: Int,
val direction: Direction,
- val shouldBeAnimated: () -> Boolean = { true }
+ val shouldBeAnimated: () -> Boolean = { true },
+ val translateFunc: (View, Float) -> Unit = { view, value -> view.translationX = value },
)
/**
* Represents a view whose animation process is in-progress. It should be immutable because the
* started animation should be completed.
*/
- private data class ViewToTranslate(val view: WeakReference<View>, val direction: Direction)
+ private data class ViewToTranslate(
+ val view: WeakReference<View>,
+ val direction: Direction,
+ val translateFunc: (View, Float) -> Unit,
+ )
/** Direction of the animation. */
enum class Direction(val multiplier: Float) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d9d64ad..376e27c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,8 +1,14 @@
package com.android.keyguard;
import static android.view.View.ALPHA;
+import static android.view.View.SCALE_X;
+import static android.view.View.SCALE_Y;
import static android.view.View.TRANSLATION_Y;
+import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
+import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
+import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -17,6 +23,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
+import androidx.core.content.res.ResourcesCompat;
import com.android.app.animation.Interpolators;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
@@ -44,6 +51,7 @@
private static final long STATUS_AREA_START_DELAY_MILLIS = 0;
private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
+ private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -88,14 +96,18 @@
private KeyguardClockFrame mLargeClockFrame;
private ClockController mClock;
- private View mStatusArea;
+ private KeyguardStatusAreaView mStatusArea;
private int mSmartspaceTopOffset;
+ private float mWeatherClockSmartspaceScaling = 1f;
+ private int mWeatherClockSmartspaceTranslateX = 0;
+ private int mWeatherClockSmartspaceTranslateY = 0;
private int mDrawAlpha = 255;
/**
* Maintain state so that a newly connected plugin can be initialized.
*/
private float mDarkAmount;
+ private boolean mSplitShadeCentered = false;
/**
* Indicates which clock is currently displayed - should be one of {@link ClockSize}.
@@ -105,7 +117,7 @@
@VisibleForTesting AnimatorSet mClockInAnim = null;
@VisibleForTesting AnimatorSet mClockOutAnim = null;
- private AnimatorSet mStatusAreaAnim = null;
+ @VisibleForTesting AnimatorSet mStatusAreaAnim = null;
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -117,13 +129,30 @@
}
/**
- * Apply dp changes on font/scale change
+ * Apply dp changes on configuration change
*/
- public void onDensityOrFontScaleChanged() {
+ public void onConfigChanged() {
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_smartspace_top_offset);
+ mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat(
+ mContext.getResources(), R.dimen.weather_clock_smartspace_scale);
+ mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize(
+ R.dimen.weather_clock_smartspace_translateX);
+ mWeatherClockSmartspaceTranslateY = mContext.getResources().getDimensionPixelSize(
+ R.dimen.weather_clock_smartspace_translateY);
+ updateStatusArea(/* animate= */false);
+ }
+
+ /**
+ * Enable or disable split shade specific positioning
+ */
+ public void setSplitShadeCentered(boolean splitShadeCentered) {
+ if (mSplitShadeCentered != splitShadeCentered) {
+ mSplitShadeCentered = splitShadeCentered;
+ updateStatusArea(/* animate= */true);
+ }
}
@Override
@@ -134,7 +163,7 @@
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
- onDensityOrFontScaleChanged();
+ onConfigChanged();
}
@Override
@@ -182,6 +211,13 @@
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
updateClockTargetRegions();
+ updateStatusArea(/* animate= */false);
+ }
+
+ private void updateStatusArea(boolean animate) {
+ if (mDisplayedClockSize != null && mChildrenAreLaidOut) {
+ updateClockViews(mDisplayedClockSize == LARGE, animate);
+ }
}
void updateClockTargetRegions() {
@@ -230,13 +266,25 @@
mStatusAreaAnim = null;
View in, out;
- float statusAreaYTranslation, clockInYTranslation, clockOutYTranslation;
+ float statusAreaYTranslation, statusAreaClockScale = 1f;
+ float statusAreaClockTranslateX = 0f, statusAreaClockTranslateY = 0f;
+ float clockInYTranslation, clockOutYTranslation;
if (useLargeClock) {
out = mSmallClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in, 0);
statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
+ // TODO: Load from clock config when less risky
+ if (mClock != null
+ && mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) {
+ statusAreaClockScale = mWeatherClockSmartspaceScaling;
+ statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX;
+ statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY;
+ if (mSplitShadeCentered) {
+ statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER;
+ }
+ }
clockInYTranslation = 0;
clockOutYTranslation = 0; // Small clock translation is handled with statusArea
} else {
@@ -258,7 +306,12 @@
in.setAlpha(1f);
in.setTranslationY(clockInYTranslation);
in.setVisibility(View.VISIBLE);
- mStatusArea.setTranslationY(statusAreaYTranslation);
+ mStatusArea.setScaleX(statusAreaClockScale);
+ mStatusArea.setScaleY(statusAreaClockScale);
+ mStatusArea.setTranslateXFromClockDesign(statusAreaClockTranslateX);
+ mStatusArea.setTranslateYFromClockDesign(statusAreaClockTranslateY);
+ mStatusArea.setTranslateYFromClockSize(statusAreaYTranslation);
+ mSmallClockFrame.setTranslationY(statusAreaYTranslation);
return;
}
@@ -295,8 +348,15 @@
useLargeClock ? STATUS_AREA_MOVE_UP_MILLIS : STATUS_AREA_MOVE_DOWN_MILLIS);
mStatusAreaAnim.setInterpolator(Interpolators.EMPHASIZED);
mStatusAreaAnim.playTogether(
- ObjectAnimator.ofFloat(mStatusArea, TRANSLATION_Y, statusAreaYTranslation),
- ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation));
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATE_Y_CLOCK_SIZE.getProperty(),
+ statusAreaYTranslation),
+ ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation),
+ ObjectAnimator.ofFloat(mStatusArea, SCALE_X, statusAreaClockScale),
+ ObjectAnimator.ofFloat(mStatusArea, SCALE_Y, statusAreaClockScale),
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATE_X_CLOCK_DESIGN.getProperty(),
+ statusAreaClockTranslateX),
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATE_Y_CLOCK_DESIGN.getProperty(),
+ statusAreaClockTranslateY));
mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
mStatusAreaAnim = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 99e2574..41c1eda 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -330,10 +330,10 @@
}
/**
- * Apply dp changes on font/scale change
+ * Apply dp changes on configuration change
*/
- public void onDensityOrFontScaleChanged() {
- mView.onDensityOrFontScaleChanged();
+ public void onConfigChanged() {
+ mView.onConfigChanged();
mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mKeyguardLargeClockTopMargin =
@@ -344,6 +344,12 @@
setDateWeatherVisibility();
}
+ /**
+ * Enable or disable split shade center specific positioning
+ */
+ public void setSplitShadeCentered(boolean splitShadeCentered) {
+ mView.setSplitShadeCentered(splitShadeCentered);
+ }
/**
* Set which clock should be displayed on the keyguard. The other one will be automatically
@@ -407,7 +413,7 @@
scale, props, animate);
if (mStatusArea != null) {
- PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
+ PropertyAnimator.setProperty(mStatusArea, KeyguardStatusAreaView.TRANSLATE_X_AOD,
x, props, animate);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 0332c9f..363dd01 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -208,7 +208,7 @@
@Override
public void run() {
final View host = mHost.get();
- if (host != null) {
+ if (host != null && host.isVisibleToUser()) {
host.announceForAccessibility(mTextToAnnounce);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index b5e5420..74b29a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -264,13 +264,6 @@
*/
@Override
public void finish(boolean fromPrimaryAuth, int targetUserId) {
- if (!mKeyguardStateController.canDismissLockScreen() && !fromPrimaryAuth) {
- Log.e(TAG,
- "Tried to dismiss keyguard when lockscreen is not dismissible and user "
- + "was not authenticated with a primary security method "
- + "(pin/password/pattern).");
- return;
- }
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt
new file mode 100644
index 0000000..e7da2b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt
@@ -0,0 +1,118 @@
+package com.android.keyguard
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.widget.LinearLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.AnimatableProperty
+
+class KeyguardStatusAreaView(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : LinearLayout(context, attrs) {
+ var translateXFromClockDesign = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationX = translateXFromAod + translateXFromClockDesign + translateXFromUnfold
+ }
+
+ var translateXFromAod = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationX = translateXFromAod + translateXFromClockDesign + translateXFromUnfold
+ }
+
+ var translateXFromUnfold = 0F
+ get() = field
+ set(value) {
+ field = value
+ translationX = translateXFromAod + translateXFromClockDesign + translateXFromUnfold
+ }
+
+ var translateYFromClockSize = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationY = value + translateYFromClockDesign
+ }
+
+ var translateYFromClockDesign = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationY = value + translateYFromClockSize
+ }
+
+ companion object {
+ @JvmField
+ val TRANSLATE_X_CLOCK_DESIGN =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateXClockDesign") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateXFromClockDesign = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateXFromClockDesign
+ }
+ },
+ R.id.translate_x_clock_design_animator_tag,
+ R.id.translate_x_clock_design_animator_start_tag,
+ R.id.translate_x_clock_design_animator_end_tag
+ )
+
+ @JvmField
+ val TRANSLATE_X_AOD =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateXAod") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateXFromAod = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateXFromAod
+ }
+ },
+ R.id.translate_x_aod_animator_tag,
+ R.id.translate_x_aod_animator_start_tag,
+ R.id.translate_x_aod_animator_end_tag
+ )
+
+ @JvmField
+ val TRANSLATE_Y_CLOCK_SIZE =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateYClockSize") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateYFromClockSize = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateYFromClockSize
+ }
+ },
+ R.id.translate_y_clock_size_animator_tag,
+ R.id.translate_y_clock_size_animator_start_tag,
+ R.id.translate_y_clock_size_animator_end_tag
+ )
+
+ @JvmField
+ val TRANSLATE_Y_CLOCK_DESIGN =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateYClockDesign") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateYFromClockDesign = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateYFromClockDesign
+ }
+ },
+ R.id.translate_y_clock_design_animator_tag,
+ R.id.translate_y_clock_design_animator_start_tag,
+ R.id.translate_y_clock_design_animator_end_tag
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 835cc13..00500d6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -24,6 +24,7 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.transition.ChangeBounds;
import android.transition.Transition;
@@ -280,8 +281,8 @@
}
@Override
- public void onDensityOrFontScaleChanged() {
- mKeyguardClockSwitchController.onDensityOrFontScaleChanged();
+ public void onConfigChanged(Configuration newConfig) {
+ mKeyguardClockSwitchController.onConfigChanged();
}
};
@@ -329,6 +330,7 @@
boolean splitShadeEnabled,
boolean shouldBeCentered,
boolean animate) {
+ mKeyguardClockSwitchController.setSplitShadeCentered(splitShadeEnabled && shouldBeCentered);
if (mStatusViewCentered == shouldBeCentered) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index edd150c..ca64ae0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -53,7 +53,10 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard),
+ ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard,
+ { view, value ->
+ (view as? KeyguardStatusAreaView)?.translateXFromUnfold = value
+ }),
ViewIdToTranslate(
R.id.lockscreen_clock_view_large, START, filterKeyguardAndSplitShadeOnly),
ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterKeyguard),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 83c317f..2bf8fb3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3668,7 +3668,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 */
@@ -3703,7 +3704,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/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 7cedecc..239a0cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -326,7 +326,7 @@
}
if (!Objects.equals(prevContentDescription, mView.getContentDescription())
- && mView.getContentDescription() != null) {
+ && mView.getContentDescription() != null && mView.isVisibleToUser()) {
mView.announceForAccessibility(mView.getContentDescription());
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 4557b34..500910c 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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 8af92ce..806d1af 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -34,6 +34,7 @@
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Handler;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.Log;
import android.view.MotionEvent;
@@ -277,9 +278,11 @@
// invalidate the view's own bounds all the way up the view hierarchy
public static void invalidateGlobalRegion(View view) {
+ Trace.beginSection("SwipeHelper.invalidateGlobalRegion");
invalidateGlobalRegion(
view,
new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+ Trace.endSection();
}
// invalidate a rectangle relative to the view's coordinate system all the way up the view
@@ -492,7 +495,7 @@
}
if (!mCancelled || wasRemoved) {
mCallback.onChildDismissed(animView);
- resetSwipeOfView(animView);
+ resetViewIfSwiping(animView);
}
if (endAction != null) {
endAction.accept(mCancelled);
@@ -547,7 +550,7 @@
if (!cancelled) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
- resetSwipeOfView(animView);
+ resetViewIfSwiping(animView);
// Clear the snapped view after success, assuming it's not being swiped now
if (animView == mTouchedView && !mIsSwiping) {
mTouchedView = null;
@@ -811,7 +814,7 @@
return mIsSwiping ? mTouchedView : null;
}
- protected void resetSwipeOfView(View view) {
+ protected void resetViewIfSwiping(View view) {
if (getSwipedView() == view) {
resetSwipeState();
}
@@ -825,6 +828,12 @@
resetSwipeStates(/* resetAll= */ true);
}
+ public void forceResetSwipeState(@NonNull View view) {
+ if (view.getTranslationX() == 0) return;
+ setTranslation(view, 0);
+ updateSwipeProgressFromOffset(view, /* dismissable= */ true, 0);
+ }
+
/** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */
private void resetSwipeStates(boolean resetAll) {
final View touchedView = mTouchedView;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 1fbbe1c..f3403f7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -93,7 +93,7 @@
// TODO(b/277338665): Tracking Bug
@JvmField
val NOTIFICATION_SHELF_REFACTOR =
- unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
+ unreleasedFlag(271161129, "notification_shelf_refactor")
@JvmField
val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -263,6 +263,11 @@
@JvmField
val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps")
+ /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
+ // TODO(b/286563884): Tracking bug
+ @JvmField
+ val KEYGUARD_TALKBACK_FIX = unreleasedFlag(238, "keyguard_talkback_fix")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6948c8d..0e0cf74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -697,6 +697,9 @@
}
}
break;
+ case TelephonyManager.SIM_STATE_UNKNOWN:
+ mPendingPinLock = false;
+ break;
default:
if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState);
break;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index db23109..1c2e85b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -404,8 +404,7 @@
val darkClockColor = wallpaperColorScheme?.accent2?.s600
/** Note that when [wallpaperColors] is null, isWallpaperDark is true. */
val isWallpaperDark: Boolean =
- (wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
- WallpaperColors.HINT_SUPPORTS_DARK_TEXT
+ (wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
clock.events.onSeedColorChanged(
if (isWallpaperDark) lightClockColor else darkClockColor
)
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/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 88ffa8d..318cd99 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -24,7 +24,6 @@
import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -175,9 +174,8 @@
mCurrentActivePosition = position;
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device));
- initMutingExpectedDevice();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
- && device.hasSubtext()) {
+ initFakeActiveDevice();
+ } else if (device.hasSubtext()) {
boolean isActiveWithOngoingSession =
(device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
mController.getSelectedMediaDevice(), device)));
@@ -267,6 +265,27 @@
setUpDeviceIcon(device);
updateFullItemClickListener(v -> cancelMuteAwaitConnection());
setSingleLineLayout(getItemTitle(device));
+ } else if (device.hasOngoingSession()) {
+ mCurrentActivePosition = position;
+ if (device.isHostForOngoingSession()) {
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ updateEndClickAreaAsSessionEditing(device);
+ mEndClickIcon.setVisibility(View.VISIBLE);
+ setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+ false /* showProgressBar */, false /* showCheckBox */,
+ true /* showEndTouchArea */);
+ initSeekbar(device, isCurrentSeekbarInvisible);
+ } else {
+ updateDeviceStatusIcon(mContext.getDrawable(
+ R.drawable.ic_sound_bars_anim));
+ mStatusIcon.setVisibility(View.VISIBLE);
+ updateSingleLineLayoutContentAlpha(
+ updateClickActionBasedOnSelectionBehavior(device)
+ ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
+ setSingleLineLayout(getItemTitle(device));
+ initFakeActiveDevice();
+ }
} else if (mController.isCurrentConnectedDeviceRemote()
&& !mController.getSelectableMediaDevice().isEmpty()) {
//If device is connected and there's other selectable devices, layout as
@@ -351,7 +370,7 @@
ColorStateList.valueOf(mController.getColorItemContent()));
mEndClickIcon.setOnClickListener(
v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
- mEndTouchArea.setOnClickListener(v -> mCheckBox.performClick());
+ mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
}
public void updateEndClickAreaColor(int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 01f7904..b88eba9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -188,6 +188,7 @@
mContainerLayout.setContentDescription(null);
mTitleText.setTextColor(mController.getColorItemContent());
mSubTitleText.setTextColor(mController.getColorItemContent());
+ mSubTitleText.setSelected(true);
mTwoLineTitleText.setTextColor(mController.getColorItemContent());
mVolumeValueText.setTextColor(mController.getColorItemContent());
mSeekBar.setProgressTintList(
@@ -417,7 +418,7 @@
mIconAreaLayout.setOnClickListener(listener);
}
- void initMutingExpectedDevice() {
+ void initFakeActiveDevice() {
disableSeekBar();
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
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 e524189..8b3d7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.navigationbar.gestural;
-import static android.view.InputDevice.SOURCE_MOUSE;
-import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
@@ -951,12 +949,10 @@
mMLResults = 0;
mLogGesture = false;
mInRejectedExclusion = false;
- // Trackpad back gestures don't have zones, so we don't need to check if the down event
- // is within insets. Also we don't allow back for button press from the trackpad, and
- // yet we do with a mouse.
boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
+ // Trackpad back gestures don't have zones, so we don't need to check if the down event
+ // is within insets.
mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
- && !isButtonPressFromTrackpad(ev)
&& (isTrackpadMultiFingerSwipe || isWithinInsets)
&& !mGestureBlockingActivityRunning
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
@@ -1069,11 +1065,6 @@
mProtoTracer.scheduleFrameUpdate();
}
- private boolean isButtonPressFromTrackpad(MotionEvent ev) {
- int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
- return (sources & (SOURCE_MOUSE | SOURCE_TOUCHPAD)) == sources && ev.getButtonState() != 0;
- }
-
private void dispatchToBackAnimation(MotionEvent event) {
if (mBackAnimation != null) {
mVelocityTracker.addMovement(event);
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 25272ae..ccfbaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -215,7 +215,7 @@
debugLog { "onShowNoteTask - opened as app bubble: $info" }
}
is NoteTaskLaunchMode.Activity -> {
- if (activityManager.isInForeground(info.packageName)) {
+ if (info.isKeyguardLocked && activityManager.isInForeground(info.packageName)) {
// Force note task into background by calling home.
val intent = createHomeIntent()
context.startActivityAsUser(intent, user)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index fae325c..4420002 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -25,12 +25,14 @@
* An entry point represents where the note task has ben called from. In rare cases, it may
* represent a "re-entry" (i.e., [APP_CLIPS]).
*/
-enum class
-NoteTaskEntryPoint {
+enum class NoteTaskEntryPoint {
/** @see [LaunchNoteTaskActivity] */
WIDGET_PICKER_SHORTCUT,
+ /** @see [LaunchNoteTaskActivity] */
+ WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+
/** @see [NoteTaskQuickAffordanceConfig] */
QUICK_AFFORDANCE,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
index 48a5933..a79057e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -22,6 +22,8 @@
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
@@ -41,40 +43,45 @@
/** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
fun logNoteTaskOpened(info: NoteTaskInfo) {
val event =
- when (info.entryPoint) {
- TAIL_BUTTON -> {
- if (info.isKeyguardLocked) {
- NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
- } else {
- NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+ when (info.entryPoint) {
+ TAIL_BUTTON -> {
+ if (info.isKeyguardLocked) {
+ NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+ } else {
+ NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+ }
}
+
+ WIDGET_PICKER_SHORTCUT,
+ WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT
+
+ QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+ APP_CLIPS,
+ KEYBOARD_SHORTCUT,
+ null -> return
}
- WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
- QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
- APP_CLIPS -> return
- KEYBOARD_SHORTCUT -> return
- null -> return
- }
uiEventLogger.log(event, info.uid, info.packageName)
}
/** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
fun logNoteTaskClosed(info: NoteTaskInfo) {
val event =
- when (info.entryPoint) {
- TAIL_BUTTON -> {
- if (info.isKeyguardLocked) {
- NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
- } else {
- NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+ when (info.entryPoint) {
+ TAIL_BUTTON -> {
+ if (info.isKeyguardLocked) {
+ NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+ } else {
+ NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+ }
}
+
+ WIDGET_PICKER_SHORTCUT,
+ WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+ QUICK_AFFORDANCE,
+ APP_CLIPS,
+ KEYBOARD_SHORTCUT,
+ null -> return
}
- WIDGET_PICKER_SHORTCUT -> return
- QUICK_AFFORDANCE -> return
- APP_CLIPS -> return
- KEYBOARD_SHORTCUT -> return
- null -> return
- }
uiEventLogger.log(event, info.uid, info.packageName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
index a758347..269eb87 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
@@ -16,6 +16,7 @@
package com.android.systemui.notetask
import android.os.UserHandle
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
/** Contextual information required to launch a Note Task by [NoteTaskController]. */
data class NoteTaskInfo(
@@ -27,7 +28,7 @@
) {
val launchMode: NoteTaskLaunchMode =
- if (isKeyguardLocked) {
+ if (isKeyguardLocked || entryPoint == WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE) {
NoteTaskLaunchMode.Activity
} else {
NoteTaskLaunchMode.AppBubble
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 441b9f5..754c365 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -51,7 +51,8 @@
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
- .setIntent(LaunchNoteTaskActivity.newIntent(context = context))
+ .setIntent(LaunchNoteTaskActivity.createIntent(context))
+ .setActivity(LaunchNoteTaskActivity.createComponent(context))
.setShortLabel(context.getString(R.string.note_task_button_label))
.setLongLived(true)
.setIcon(icon)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 8ca13b9..7ef149d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -16,6 +16,7 @@
package com.android.systemui.notetask.shortcut
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -72,7 +73,13 @@
controller.startNoteTaskProxyActivityForUser(mainUser)
}
} else {
- controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
+ val entryPoint =
+ if (isInMultiWindowMode) {
+ NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
+ } else {
+ NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+ }
+ controller.showNoteTask(entryPoint)
}
finish()
}
@@ -80,11 +87,14 @@
companion object {
/** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
- fun newIntent(context: Context): Intent {
- return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+ fun createIntent(context: Context): Intent =
+ Intent(context, LaunchNoteTaskActivity::class.java).apply {
// Intent's action must be set in shortcuts, or an exception will be thrown.
action = Intent.ACTION_CREATE_NOTE
}
- }
+
+ /** Creates a new [ComponentName] for [LaunchNoteTaskActivity]. */
+ fun createComponent(context: Context): ComponentName =
+ ComponentName(context, LaunchNoteTaskActivity::class.java)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 17a8870..d2e94d6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1653,10 +1653,9 @@
Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding));
mKeyguardNotificationBottomPadding = bottomPadding;
- float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
- // getMinStackScrollerPadding is from the top of the screen,
- // but we need it from the top of the NSSL.
- - mNotificationStackScrollLayoutController.getTop();
+ float staticTopPadding = mClockPositionAlgorithm.getLockscreenNotifPadding(
+ mNotificationStackScrollLayoutController.getTop());
+
mKeyguardNotificationTopPadding = staticTopPadding;
// To debug the available space, enable debug lines in this class. If you change how the
@@ -1670,8 +1669,8 @@
Log.i(TAG, "\n");
Log.i(TAG, "staticTopPadding[" + staticTopPadding
+ "] = Clock.padding["
- + mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
- + "] - NSSLC.top[" + mNotificationStackScrollLayoutController.getTop()
+ + mClockPositionAlgorithm.getLockscreenNotifPadding(
+ mNotificationStackScrollLayoutController.getTop())
+ "]"
);
Log.i(TAG, "bottomPadding[" + bottomPadding
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/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f7c6594..df6fe3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4011,7 +4011,7 @@
mCentralSurfaces.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
- cancelActiveSwipe();
+ resetAllSwipeState();
}
}
@@ -4077,7 +4077,7 @@
mGroupExpansionManager.collapseGroups();
mExpandHelper.cancelImmediately();
if (!mIsExpansionChanging) {
- cancelActiveSwipe();
+ resetAllSwipeState();
}
finalizeClearAllAnimation();
}
@@ -4406,7 +4406,7 @@
boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll();
if (nowFullyHidden != wasFullyHidden) {
updateVisibility();
- mSwipeHelper.resetTouchState();
+ resetAllSwipeState();
}
if (!wasHiddenAtAll && nowHiddenAtAll) {
resetExposedMenuView(true /* animate */, true /* animate */);
@@ -5866,9 +5866,14 @@
}
}
- private void cancelActiveSwipe() {
+ private void resetAllSwipeState() {
+ Trace.beginSection("NSSL.resetAllSwipeState()");
mSwipeHelper.resetTouchState();
+ for (int i = 0; i < getChildCount(); i++) {
+ mSwipeHelper.forceResetSwipeState(getChildAt(i));
+ }
updateContinuousShadowDrawing();
+ Trace.endSection();
}
void updateContinuousShadowDrawing() {
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..efb7926 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
@@ -315,7 +315,8 @@
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
if (mClipNotificationScrollToTop
- && ((isHeadsUp && !firstHeadsUp) || child.isHeadsUpAnimatingAway())
+ && !firstHeadsUp
+ && (isHeadsUp || child.isHeadsUpAnimatingAway())
&& newNotificationEnd > firstHeadsUpEnd
&& !ambientState.isShadeExpanded()) {
// The bottom of this view is peeking out from under the previous view.
@@ -619,13 +620,12 @@
updateViewWithShelf(view, viewState, shelfStart);
}
}
- // Avoid pulsing notification flicker during AOD to LS
- // A pulsing notification is already expanded, no need to expand it again with animation
- if (ambientState.isPulsingRow(view)) {
- expansionFraction = 1.0f;
+ viewState.height = getMaxAllowedChildHeight(view);
+ if (!view.isPinned() && !view.isHeadsUpAnimatingAway()
+ && !ambientState.isPulsingRow(view)) {
+ // The expansion fraction should not affect HUNs or pulsing notifications.
+ viewState.height *= expansionFraction;
}
- // Clip height of view right before shelf.
- viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
algorithmState.mCurrentYPosition +=
@@ -785,6 +785,8 @@
}
}
if (row.isPinned()) {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
childState.setYTranslation(
Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
@@ -809,7 +811,11 @@
}
}
if (row.isHeadsUpAnimatingAway()) {
- childState.setYTranslation(Math.max(childState.getYTranslation(), mHeadsUpInset));
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ // keep it visible for the animation
childState.hidden = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 561bd91..fc3c85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -229,12 +229,18 @@
}
}
- public float getLockscreenMinStackScrollerPadding() {
+ /**
+ * @param nsslTop NotificationStackScrollLayout top, which is below top of the srceen.
+ * @return Distance from nsslTop to top of the first view in the lockscreen shade.
+ */
+ public float getLockscreenNotifPadding(float nsslTop) {
if (mBypassEnabled) {
- return mUnlockedStackScrollerPadding;
+ return mUnlockedStackScrollerPadding - nsslTop;
} else if (mIsSplitShade) {
- return mSplitShadeTargetTopMargin + mUserSwitchHeight;
+ return mSplitShadeTargetTopMargin + mUserSwitchHeight - nsslTop;
} else {
+ // Non-bypass portrait shade already uses values from nsslTop
+ // so we don't need to subtract it here.
return mMinTopMargin + mKeyguardStatusHeight;
}
}
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 ed8050a..92a7854 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/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 254f953..061340e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -72,6 +72,7 @@
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
+ private KeyguardStatusAreaView mStatusArea;
KeyguardClockSwitch mKeyguardClockSwitch;
@@ -109,6 +110,7 @@
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
+ mStatusArea = mKeyguardClockSwitch.findViewById(R.id.keyguard_status_area);
mKeyguardClockSwitch.mChildrenAreLaidOut = true;
}
@@ -185,6 +187,7 @@
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
+ mKeyguardClockSwitch.mStatusAreaAnim.end();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
@@ -206,6 +209,7 @@
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
+ mKeyguardClockSwitch.mStatusAreaAnim.end();
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
@@ -226,6 +230,31 @@
}
@Test
+ public void switchingToSmallClockAnimation_resetsStatusArea() {
+ mKeyguardClockSwitch.switchToClock(SMALL, true);
+
+ mKeyguardClockSwitch.mClockInAnim.end();
+ mKeyguardClockSwitch.mClockOutAnim.end();
+ mKeyguardClockSwitch.mStatusAreaAnim.end();
+
+ assertThat(mStatusArea.getTranslationX()).isEqualTo(0);
+ assertThat(mStatusArea.getTranslationY()).isEqualTo(0);
+ assertThat(mStatusArea.getScaleX()).isEqualTo(1);
+ assertThat(mStatusArea.getScaleY()).isEqualTo(1);
+ }
+
+ @Test
+ public void switchingToSmallClockNoAnimation_resetsStatusArea() {
+ mKeyguardClockSwitch.switchToClock(SMALL, false);
+
+ assertThat(mStatusArea.getTranslationX()).isEqualTo(0);
+ assertThat(mStatusArea.getTranslationY()).isEqualTo(0);
+ assertThat(mStatusArea.getScaleX()).isEqualTo(1);
+ assertThat(mStatusArea.getScaleY()).isEqualTo(1);
+ }
+
+
+ @Test
public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d2e5a45..2f20f76 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -585,21 +585,6 @@
}
@Test
- public void testSecurityCallbackFinish() {
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
- when(mKeyguardUpdateMonitor.isUserUnlocked(0)).thenReturn(true);
- mKeyguardSecurityContainerController.finish(true, 0);
- verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
- }
-
- @Test
- public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() {
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
- mKeyguardSecurityContainerController.finish(false, 0);
- verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
- }
-
- @Test
public void testOnStartingToHide() {
mKeyguardSecurityContainerController.onStartingToHide();
verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
new file mode 100644
index 0000000..e6b6964
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
@@ -0,0 +1,44 @@
+package com.android.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusAreaViewTest : SysuiTestCase() {
+
+ private lateinit var view: KeyguardStatusAreaView
+
+ @Before
+ fun setUp() {
+ view = KeyguardStatusAreaView(context)
+ }
+
+ @Test
+ fun checkTranslationX_AddedTotals() {
+ view.translateXFromClockDesign = 10f
+ assertEquals(10f, view.translationX)
+
+ view.translateXFromAod = 20f
+ assertEquals(30f, view.translationX)
+
+ view.translateXFromUnfold = 30f
+ assertEquals(60f, view.translationX)
+ }
+
+ @Test
+ fun checkTranslationY_AddedTotals() {
+ view.translateYFromClockSize = 10f
+ assertEquals(10f, view.translationY)
+
+ view.translateYFromClockDesign = 20f
+ assertEquals(30f, view.translationY)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 419d045..de306d6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2800,6 +2800,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(),
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/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/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 6d8c9b1..7df54d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -280,6 +280,50 @@
}
@Test
+ public void onBindViewHolder_bindConnectedRemoteDeviceWithOnGoingSession_verifyView() {
+ when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+ ImmutableList.of());
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onBindViewHolder_bindConnectedRemoteDeviceWithHostOnGoingSession_verifyView() {
+ when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
+ when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+ ImmutableList.of());
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 9dba9b5..f8971fd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -136,7 +136,10 @@
@After
public void tearDown() {
- mMediaOutputBroadcastDialog.dismissDialog();
+ if (mMediaOutputBroadcastDialog.mAlertDialog != null){
+ mMediaOutputBroadcastDialog.mAlertDialog.dismiss();
+ }
+ mMediaOutputBroadcastDialog.dismiss();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index e99f8b6..0954f6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -72,6 +72,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -227,31 +228,7 @@
// region showNoteTask
@Test
- fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
- val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
- whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
- whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
-
- createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
-
- val intentCaptor = argumentCaptor<Intent>()
- val userCaptor = argumentCaptor<UserHandle>()
- verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- assertThat(intentCaptor.value).run {
- hasAction(ACTION_CREATE_NOTE)
- hasPackage(NOTE_TASK_PACKAGE_NAME)
- hasFlags(FLAG_ACTIVITY_NEW_TASK)
- hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
- hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
- extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
- }
- assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
- verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles)
- }
-
- @Test
- fun showNoteTaskWithUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
+ fun showNoteTaskAsUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
val user10 = UserHandle.of(/* userId= */ 10)
val expectedInfo =
NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10)
@@ -278,6 +255,30 @@
}
@Test
+ fun showNoteTask_keyguardIsLocked_notesIsClosed_shouldStartActivityAndLogUiEvent() {
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+ whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
+
+ val intentCaptor = argumentCaptor<Intent>()
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_CREATE_NOTE)
+ hasPackage(NOTE_TASK_PACKAGE_NAME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
+ }
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
+ verify(eventLogger).logNoteTaskOpened(expectedInfo)
+ verifyZeroInteractions(bubbles)
+ }
+
+ @Test
fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldCloseActivityAndLogUiEvent() {
val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
@@ -301,7 +302,7 @@
}
@Test
- fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
+ fun showNoteTask_keyguardIsUnlocked_noteIsClosed_shouldStartBubblesWithoutLoggingUiEvent() {
val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
@@ -309,7 +310,23 @@
createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
// Context package name used to create bubble icon from drawable resource id
- verify(context).packageName
+ verify(context, atLeastOnce()).packageName
+ verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
+ verifyZeroInteractions(eventLogger)
+ }
+
+ @Test
+ fun showNoteTask_keyguardIsUnlocked_noteIsOpen_shouldStartBubblesWithoutLoggingUiEvent() {
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
+ whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+ whenever(activityManager.getRunningTasks(anyInt()))
+ .thenReturn(listOf(NOTE_RUNNING_TASK_INFO))
+
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
+
+ // Context package name used to create bubble icon from drawable resource id
+ verify(context, atLeastOnce()).packageName
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
verifyZeroInteractions(eventLogger)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
index 3435450..24f39d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
@@ -16,37 +16,47 @@
package com.android.systemui.notetask
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
/** atest SystemUITests:NoteTaskInfoTest */
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(AndroidTestingRunner::class)
internal class NoteTaskInfoTest : SysuiTestCase() {
- private fun createNoteTaskInfo(): NoteTaskInfo =
- NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID, UserHandle.of(0))
-
@Test
fun launchMode_keyguardLocked_launchModeActivity() {
- val underTest = createNoteTaskInfo().copy(isKeyguardLocked = true)
+ val underTest = DEFAULT_INFO.copy(isKeyguardLocked = true)
assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
}
@Test
- fun launchMode_keyguardUnlocked_launchModeActivity() {
- val underTest = createNoteTaskInfo().copy(isKeyguardLocked = false)
+ fun launchMode_multiWindowMode_launchModeActivity() {
+ val underTest = DEFAULT_INFO.copy(entryPoint = WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE)
+
+ assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+ }
+
+ @Test
+ fun launchMode_keyguardUnlocked_launchModeAppBubble() {
+ val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false)
assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble)
}
private companion object {
- const val NOTES_PACKAGE_NAME = "com.android.note.app"
- const val NOTES_UID = 123456
+
+ val DEFAULT_INFO =
+ NoteTaskInfo(
+ packageName = "com.android.note.app",
+ uid = 123456,
+ user = UserHandle.of(0),
+ )
}
}
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/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 7632d01..df65c09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -29,6 +30,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
@@ -670,6 +672,28 @@
}
@Test
+ public void testForceResetSwipeStateDoesNothingIfTranslationIsZero() {
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+ doReturn(0f).when(mNotificationRow).getTranslationX();
+
+ mSwipeHelper.forceResetSwipeState(mNotificationRow);
+
+ verify(mNotificationRow).getTranslationX();
+ verifyNoMoreInteractions(mNotificationRow);
+ }
+
+ @Test
+ public void testForceResetSwipeStateResetsTranslationAndAlpha() {
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+ doReturn(10f).when(mNotificationRow).getTranslationX();
+
+ mSwipeHelper.forceResetSwipeState(mNotificationRow);
+
+ verify(mNotificationRow).setTranslation(eq(0f));
+ verify(mNotificationRow).setContentAlpha(eq(1f));
+ }
+
+ @Test
public void testContentAlphaRemainsUnchangedWhenFeatureFlagIsDisabled() {
// Returning true prevents alpha fade. In an unmocked scenario the callback is instantiated
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..88b1eb3 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
@@ -35,7 +35,6 @@
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
-
@JvmField @Rule
var expect: Expect = Expect.create()
@@ -82,26 +81,58 @@
fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
-
- stackScrollAlgorithm.resetViewStates(ambientState, 0)
-
- assertThat(notificationRow.viewState.yTranslation)
- .isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ resetViewStates_hunYTranslationIsInset()
}
@Test
- fun resetViewStates_stackMargin_changesHunYTranslation() {
+ fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
- val minHeadsUpTranslation = context.resources
- .getDimensionPixelSize(R.dimen.notification_side_paddings)
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
- // split shade case with top margin introduced by shade's status bar
- ambientState.stackTopMargin = 100
- stackScrollAlgorithm.resetViewStates(ambientState, 0)
+ @Test
+ fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ resetViewStates_hunYTranslationIsInset()
+ }
- // top margin presence should decrease heads up translation up to minHeadsUpTranslation
- assertThat(notificationRow.viewState.yTranslation).isEqualTo(minHeadsUpTranslation)
+ @Test
+ fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
+
+ @Test
+ fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+ assertThat(notificationRow.viewState.clipBottomAmount).isEqualTo(0)
+ }
+
+ @Test
+ fun resetViewStates_hunsOverlapping_bottomHunClipped() {
+ val topHun = mockExpandableNotificationRow()
+ val bottomHun = mockExpandableNotificationRow()
+ whenever(topHun.isHeadsUp).thenReturn(true)
+ whenever(topHun.isPinned).thenReturn(true)
+ whenever(bottomHun.isHeadsUp).thenReturn(true)
+ whenever(bottomHun.isPinned).thenReturn(true)
+
+ resetViewStates_hunsOverlapping_bottomHunClipped(topHun, bottomHun)
+ }
+
+ @Test
+ fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
+ val topHun = mockExpandableNotificationRow()
+ val bottomHun = mockExpandableNotificationRow()
+ whenever(topHun.isHeadsUp).thenReturn(true)
+ whenever(topHun.isPinned).thenReturn(true)
+ whenever(bottomHun.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunsOverlapping_bottomHunClipped(topHun, bottomHun)
}
@Test
@@ -855,6 +886,57 @@
ambientState.stackHeight = ambientState.stackEndHeight * fraction
}
+ private fun resetViewStates_hunYTranslationIsInset() {
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ assertThat(notificationRow.viewState.yTranslation)
+ .isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ private fun resetViewStates_stackMargin_changesHunYTranslation() {
+ val stackTopMargin = 50
+ val headsUpTranslationY = stackScrollAlgorithm.mHeadsUpInset - stackTopMargin
+
+ // we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
+ ambientState.shelf = notificationShelf
+
+ // split shade case with top margin introduced by shade's status bar
+ ambientState.stackTopMargin = stackTopMargin
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ // heads up translation should be decreased by the top margin
+ assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTranslationY)
+ }
+
+ private fun resetViewStates_hunsOverlapping_bottomHunClipped(
+ topHun: ExpandableNotificationRow,
+ bottomHun: ExpandableNotificationRow
+ ) {
+ val topHunHeight = mContext.resources.getDimensionPixelSize(
+ R.dimen.notification_content_min_height)
+ val bottomHunHeight = mContext.resources.getDimensionPixelSize(
+ R.dimen.notification_max_heads_up_height)
+ whenever(topHun.intrinsicHeight).thenReturn(topHunHeight)
+ whenever(bottomHun.intrinsicHeight).thenReturn(bottomHunHeight)
+
+ // we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
+ ambientState.shelf = notificationShelf
+
+ // add two overlapping HUNs
+ hostView.removeAllViews()
+ hostView.addView(topHun)
+ hostView.addView(bottomHun)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+ // the height shouldn't change
+ assertThat(topHun.viewState.height).isEqualTo(topHunHeight)
+ assertThat(bottomHun.viewState.height).isEqualTo(bottomHunHeight)
+ // the HUN at the bottom should be clipped
+ assertThat(topHun.viewState.clipBottomAmount).isEqualTo(0)
+ assertThat(bottomHun.viewState.clipBottomAmount).isEqualTo(bottomHunHeight - topHunHeight)
+ }
+
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 7e69efa..5d2b59b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -67,6 +67,8 @@
private float mQsExpansion;
private int mCutoutTopInset = 0;
private boolean mIsSplitShade = false;
+ private boolean mBypassEnabled = false;
+ private int mUnlockedStackScrollerPadding = 0;
private float mUdfpsTop = -1;
private float mClockBottom = SCREEN_HEIGHT / 2;
private boolean mClockTopAligned;
@@ -339,15 +341,52 @@
}
@Test
- public void notifMinPaddingAlignedWithClockInSplitShadeMode() {
+ public void notifPadding_splitShade() {
givenLockScreen();
mIsSplitShade = true;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
// THEN the padding DOESN'T adjust for keyguard status height.
- assertThat(mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding())
- .isEqualTo(mKeyguardStatusBarHeaderHeight);
+ assertThat(mClockPositionAlgorithm.getLockscreenNotifPadding(/* nsslTop= */ 10))
+ .isEqualTo(mKeyguardStatusBarHeaderHeight - 10);
+ }
+
+ @Test
+ public void notifPadding_portraitShade_bypassOff() {
+ givenLockScreen();
+ mIsSplitShade = false;
+ mBypassEnabled = false;
+
+ // mMinTopMargin = 100 = 80 + max(20, 0)
+ mKeyguardStatusBarHeaderHeight = 80;
+ mUserSwitchHeight = 20;
+ when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
+ .thenReturn(0);
+
+ mKeyguardStatusHeight = 200;
+
+ // WHEN the position algorithm is run
+ positionClock();
+
+ // THEN padding = 300 = mMinTopMargin(100) + mKeyguardStatusHeight(200)
+ assertThat(mClockPositionAlgorithm.getLockscreenNotifPadding(/* nsslTop= */ 50))
+ .isEqualTo(300);
+ }
+
+ @Test
+ public void notifPadding_portraitShade_bypassOn() {
+ givenLockScreen();
+ mIsSplitShade = false;
+ mBypassEnabled = true;
+ mUnlockedStackScrollerPadding = 200;
+
+ // WHEN the position algorithm is run
+ positionClock();
+
+ // THEN padding = 150 = mUnlockedStackScrollerPadding(200) - nsslTop(50)
+ assertThat(mClockPositionAlgorithm.getLockscreenNotifPadding(/* nsslTop= */ 50))
+ .isEqualTo(150);
}
@Test
@@ -589,8 +628,8 @@
0 /* userSwitchPreferredY */,
mDark,
ZERO_DRAG,
- false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */,
+ mBypassEnabled,
+ mUnlockedStackScrollerPadding,
mQsExpansion,
mCutoutTopInset,
mIsSplitShade,
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/overlays/NotesRoleEnabledOverlay/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
index 68ebd96..70b783f 100644
--- a/packages/overlays/NotesRoleEnabledOverlay/Android.bp
+++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
@@ -25,6 +25,7 @@
runtime_resource_overlay {
name: "NotesRoleEnabledOverlay",
+ certificate: "platform",
theme: "NotesRoleEnabled",
product_specific: true,
}
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/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6b55d7e..cd2f844 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -987,8 +987,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);
+ }
}
/**
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index e8c85ce..c6e9a7d 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -162,6 +162,8 @@
// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
public class VcnManagementService extends IVcnManagementService.Stub {
@NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
+ @NonNull private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
+
private static final long DUMP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
private static final int LOCAL_LOG_LINE_COUNT = 512;
@@ -223,7 +225,9 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
- mContext = requireNonNull(context, "Missing context");
+ mContext =
+ requireNonNull(context, "Missing context")
+ .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
mDeps = requireNonNull(deps, "Missing dependencies");
mLooper = mDeps.getLooper();
@@ -1065,13 +1069,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/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 9b4f968..0d423d8 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -80,6 +80,7 @@
*/
public class VpnManagerService extends IVpnManager.Stub {
private static final String TAG = VpnManagerService.class.getSimpleName();
+ private static final String CONTEXT_ATTRIBUTION_TAG = "VPN_MANAGER";
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -157,7 +158,7 @@
}
public VpnManagerService(Context context, Dependencies deps) {
- mContext = context;
+ mContext = context.createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
mDeps = deps;
mHandlerThread = mDeps.makeHandlerThread();
mHandlerThread.start();
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 2812233..9917892 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -88,7 +88,7 @@
} else {
//live wallpaper
ComponentName currCN = info.getComponent();
- ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context);
+ ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context);
if (!currCN.equals(defaultCN)) {
return true;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6f20563..fc84e13 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 44e198b..8c31209 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1678,9 +1678,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 92e73f5..0916960 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;
}
}
}
@@ -14034,7 +14048,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);
}
}
@@ -14849,6 +14864,7 @@
}
}
+ final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
// Add to the sticky list if requested.
if (sticky) {
if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
@@ -14911,12 +14927,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));
}
}
@@ -14999,7 +15016,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;
@@ -15093,7 +15111,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);
@@ -15110,6 +15129,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 =
@@ -16464,6 +16496,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 add22bd..fd98072 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -290,6 +290,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":
@@ -1520,6 +1522,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;
@@ -4271,6 +4290,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 8380308..42a4a85 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>
@@ -682,10 +691,10 @@
// If this receiver is going to be skipped, skip it now itself and don't even enqueue
// it.
- final boolean wouldBeSkipped = (mSkipPolicy.shouldSkipMessage(r, receiver) != null);
- if (wouldBeSkipped) {
+ final String skipReason = mSkipPolicy.shouldSkipMessage(r, receiver);
+ if (skipReason != null) {
setDeliveryState(null, null, r, i, receiver, BroadcastRecord.DELIVERY_SKIPPED,
- "skipped by policy at enqueue");
+ "skipped by policy at enqueue: " + skipReason);
continue;
}
@@ -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(
@@ -1914,12 +1950,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"
@@ -1934,7 +1974,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 e744eee..3f7d8ba 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);
@@ -266,7 +270,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;
}
@@ -282,6 +287,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");
@@ -338,7 +345,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);
@@ -516,7 +524,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)
@@ -541,7 +550,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/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index 01fb0d1..af99684 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -392,11 +392,9 @@
if (TEMP_DUMP_TIME_LIMIT <= timeTaken) {
Slog.e(TAG, "Aborted stack trace dump (current primary pid=" + pid
+ "); deadline exceeded.");
- tmpTracesFile.delete();
if (latencyTracker != null) {
latencyTracker.dumpStackTracesTempFileTimedOut();
}
- return null;
}
if (DEBUG_ANR) {
Slog.d(TAG, "Done with primary pid " + pid + " in " + timeTaken + "ms"
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/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 1f8ff73..29a1941 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1018,13 +1018,15 @@
}
}
- /*package*/ void clearA2dpSuspended() {
+ /*package*/ void clearA2dpSuspended(boolean internalOnly) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "clearA2dpSuspended");
+ Log.v(TAG, "clearA2dpSuspended, internalOnly: " + internalOnly);
}
synchronized (mBluetoothAudioStateLock) {
mBluetoothA2dpSuspendedInt = false;
- mBluetoothA2dpSuspendedExt = false;
+ if (!internalOnly) {
+ mBluetoothA2dpSuspendedExt = false;
+ }
updateAudioHalBluetoothState();
}
}
@@ -1046,13 +1048,15 @@
}
}
- /*package*/ void clearLeAudioSuspended() {
+ /*package*/ void clearLeAudioSuspended(boolean internalOnly) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "clearLeAudioSuspended");
+ Log.v(TAG, "clearLeAudioSuspended, internalOnly: " + internalOnly);
}
synchronized (mBluetoothAudioStateLock) {
mBluetoothLeSuspendedInt = false;
- mBluetoothLeSuspendedExt = false;
+ if (!internalOnly) {
+ mBluetoothLeSuspendedExt = false;
+ }
updateAudioHalBluetoothState();
}
}
@@ -2214,6 +2218,7 @@
mDeviceInventory.removePreferredDevicesForStrategyInt(mAccessibilityStrategyId);
}
mDeviceInventory.applyConnectedDevicesRoles();
+ mDeviceInventory.reapplyExternalDevicesRoles();
} else {
mDeviceInventory.setPreferredDevicesForStrategyInt(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d1cae49..ec85d57 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -361,23 +361,34 @@
AudioSystem.DEVICE_STATE_AVAILABLE,
di.mDeviceCodecFormat);
}
- mAppliedStrategyRoles.clear();
mAppliedStrategyRolesInt.clear();
- mAppliedPresetRoles.clear();
mAppliedPresetRolesInt.clear();
applyConnectedDevicesRoles_l();
}
+ reapplyExternalDevicesRoles();
+ }
+
+ /*package*/ void reapplyExternalDevicesRoles() {
+ synchronized (mDevicesLock) {
+ mAppliedStrategyRoles.clear();
+ mAppliedPresetRoles.clear();
+ }
synchronized (mPreferredDevices) {
mPreferredDevices.forEach((strategy, devices) -> {
- setPreferredDevicesForStrategy(strategy, devices); });
+ setPreferredDevicesForStrategy(strategy, devices);
+ });
}
synchronized (mNonDefaultDevices) {
mNonDefaultDevices.forEach((strategy, devices) -> {
addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED,
- devices, false /* internal */); });
+ devices, false /* internal */);
+ });
}
synchronized (mPreferredDevicesForCapturePreset) {
- // TODO: call audiosystem to restore
+ mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
+ setDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ });
}
}
@@ -1163,6 +1174,7 @@
return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> {
return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
+ reapplyExternalDevicesRoles();
}
@GuardedBy("mDevicesLock")
@@ -1535,7 +1547,7 @@
}
// Reset A2DP suspend state each time a new sink is connected
- mDeviceBroker.clearA2dpSuspended();
+ mDeviceBroker.clearA2dpSuspended(true /* internalOnly */);
// The convention for head tracking sensors associated with A2DP devices is to
// use a UUID derived from the MAC address as follows:
@@ -1958,7 +1970,7 @@
"LE Audio device addr=" + address + " now available").printLog(TAG));
}
// Reset LEA suspend state each time a new sink is connected
- mDeviceBroker.clearLeAudioSuspended();
+ mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ac03c82..c177c4e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -353,7 +353,6 @@
private static final int MSG_PLAY_SOUND_EFFECT = 5;
private static final int MSG_LOAD_SOUND_EFFECTS = 7;
private static final int MSG_SET_FORCE_USE = 8;
- private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
private static final int MSG_SET_ALL_VOLUMES = 10;
private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
private static final int MSG_SYSTEM_READY = 16;
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index e46c3cc..c8a17e5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -445,8 +445,8 @@
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- mDeviceBroker.clearA2dpSuspended();
- mDeviceBroker.clearLeAudioSuspended();
+ mDeviceBroker.clearA2dpSuspended(false /* internalOnly */);
+ mDeviceBroker.clearLeAudioSuspended(false /* internalOnly */);
mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d2dcc50..41651fd 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -126,6 +126,7 @@
private final boolean mDreamsActivatedOnChargeByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
private final boolean mKeepDreamingWhenUnpluggingDefault;
+ private final boolean mDreamsDisabledByAmbientModeSuppressionConfig;
private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
@@ -239,6 +240,9 @@
mSettingsObserver = new SettingsObserver(mHandler);
mKeepDreamingWhenUnpluggingDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
+ mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
+
}
@Override
@@ -406,6 +410,13 @@
return false;
}
+ if (mDreamsDisabledByAmbientModeSuppressionConfig
+ && mPowerManagerInternal.isAmbientDisplaySuppressed()) {
+ // Don't dream if Bedtime (or something else) is suppressing ambient.
+ Slog.i(TAG, "Can't start dreaming because ambient is suppressed.");
+ return false;
+ }
+
if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
return mIsCharging;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b39e860..d29d9c8 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6597,7 +6597,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/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f2d1357..aaf13eb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3714,11 +3714,16 @@
if (parser.getName().equals(TAG_PERMISSIONS)) {
final LegacyPermissionState legacyState;
if (ps.hasSharedUser()) {
- legacyState = getSettingLPr(ps.getSharedUserAppId()).getLegacyPermissionState();
+ final SettingBase sharedUserSettings = getSettingLPr(
+ ps.getSharedUserAppId());
+ legacyState = sharedUserSettings != null
+ ? sharedUserSettings.getLegacyPermissionState() : null;
} else {
legacyState = ps.getLegacyPermissionState();
}
- readInstallPermissionsLPr(parser, legacyState, users);
+ if (legacyState != null) {
+ readInstallPermissionsLPr(parser, legacyState, users);
+ }
} else if (parser.getName().equals(TAG_USES_STATIC_LIB)) {
readUsesStaticLibLPw(parser, ps);
} else if (parser.getName().equals(TAG_USES_SDK_LIB)) {
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/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3e7ae33..2499529 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -141,6 +141,7 @@
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionGroup;
@@ -4538,8 +4539,13 @@
final int appId = ps.getAppId();
final LegacyPermissionState legacyState;
if (ps.hasSharedUser()) {
- legacyState = mPackageManagerInt.getSharedUserApi(
- ps.getSharedUserAppId()).getSharedUserLegacyPermissionState();
+ final int sharedUserId = ps.getSharedUserAppId();
+ SharedUserApi sharedUserApi = mPackageManagerInt.getSharedUserApi(sharedUserId);
+ if (sharedUserApi == null) {
+ Slog.wtf(TAG, "Missing shared user Api for " + sharedUserId);
+ return;
+ }
+ legacyState = sharedUserApi.getSharedUserLegacyPermissionState();
} else {
legacyState = ps.getLegacyPermissionState();
}
@@ -4584,8 +4590,13 @@
ps.setInstallPermissionsFixed(false);
final LegacyPermissionState legacyState;
if (ps.hasSharedUser()) {
- legacyState = mPackageManagerInt.getSharedUserApi(
- ps.getSharedUserAppId()).getSharedUserLegacyPermissionState();
+ final int sharedUserId = ps.getSharedUserAppId();
+ SharedUserApi sharedUserApi = mPackageManagerInt.getSharedUserApi(sharedUserId);
+ if (sharedUserApi == null) {
+ Slog.wtf(TAG, "Missing shared user Api for " + sharedUserId);
+ return;
+ }
+ legacyState = sharedUserApi.getSharedUserLegacyPermissionState();
} else {
legacyState = ps.getLegacyPermissionState();
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 695a0cf..a53b831 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -283,7 +283,7 @@
private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L;
/**
- * Apps targeting Android U and above need to define
+ * Apps targeting Android V and above need to define
* {@link android.Manifest.permission#TURN_SCREEN_ON} in their manifest for
* {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP} to have any effect.
* Note that most applications should use {@link android.R.attr#turnScreenOn} or
@@ -291,7 +291,7 @@
* previous foreground app from being resumed first when the screen turns on.
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
public static final long REQUIRE_TURN_SCREEN_ON_PERMISSION = 216114297L;
/** Reason ID for holding display suspend blocker. */
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index a079875..9e9b344 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1588,7 +1588,7 @@
mShuttingDown = false;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
- mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
+ mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9f16a844..89a4a13 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4550,6 +4550,13 @@
* immediately finishes after, so we have to transfer T to M.
*/
void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
+ final WindowState mainWin = findMainWindow(false);
+ if (mainWin != null && mainWin.mWinAnimator.getShown()) {
+ // This activity already has a visible window, so doesn't need to transfer the starting
+ // window from above activity to here. The starting window will be removed with above
+ // activity.
+ return;
+ }
task.forAllActivities(fromActivity -> {
if (fromActivity == this) return true;
return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1a322ff..5e2618b 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -454,7 +454,7 @@
if (mSource.getType() == WindowInsets.Type.ime()) {
setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
}
- final Transaction t = mDisplayContent.getSyncTransaction();
+ final Transaction t = mWindowContainer.getSyncTransaction();
mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
ANIMATION_TYPE_INSETS_CONTROL);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4995236..6bab371 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2357,10 +2357,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
@@ -3306,9 +3310,14 @@
if (aOptions != null) {
// Resolve the root task the task should be placed in now based on options
// and reparent if needed.
+ // TODO(b/229927851) For split-screen, setLaunchRootTask is no longer the "root"
+ // task, consider to rename methods like "parentTask" instead of "rootTask".
final Task targetRootTask =
getOrCreateRootTask(null, aOptions, task, onTop);
- if (targetRootTask != null && task.getRootTask() != targetRootTask) {
+ // When launch with ActivityOptions#getLaunchRootTask, the "root task" just mean the
+ // parent of current launch, not the "root task" in hierarchy.
+ if (targetRootTask != null && task.getRootTask() != targetRootTask
+ && task.getParent() != targetRootTask) {
final int reparentMode = onTop
? REPARENT_MOVE_ROOT_TASK_TO_FRONT : REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
task.reparent(targetRootTask, onTop, reparentMode, ANIMATE, DEFER_RESUME,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7104739..ceb80d6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3443,6 +3443,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 0311ff8..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) {
@@ -1190,7 +1188,11 @@
// processed all the participants first (in particular, we want to trigger pip-enter first)
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar != null) {
+ // If the activity was just inserted to an invisible task, it will keep INITIALIZING
+ // state. Then no need to notify the callback to avoid clearing some states
+ // unexpectedly, e.g. launch-task-behind.
+ if (ar != null && (ar.isVisibleRequested()
+ || !ar.isState(ActivityRecord.State.INITIALIZING))) {
mController.dispatchLegacyAppTransitionFinished(ar);
}
}
@@ -1776,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();
@@ -1798,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");
@@ -1832,6 +1836,11 @@
break;
}
+ final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
+ if (asyncRotationController != null) {
+ asyncRotationController.accept(navWindow);
+ }
+
if (animate) {
final NavBarFadeAnimationController controller =
new NavBarFadeAnimationController(dc);
@@ -1840,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() {
@@ -2313,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);
@@ -2437,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 d84c85c..67572bf 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;
@@ -3591,6 +3592,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/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 a5b1548..e44b8cd 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
@@ -7840,27 +7841,29 @@
throw new SecurityException("Cannot wipe data. " + restriction
+ " restriction is set for user " + userId);
}
+ });
- boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
- boolean wipeDevice;
- if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
- adminPackage,
- userId)) {
- // Legacy mode
- wipeDevice = isSystemUser;
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ adminPackage,
+ userId)) {
+ // Legacy mode
+ wipeDevice = isSystemUser;
+ } else {
+ // Explicit behaviour
+ if (factoryReset) {
+ EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
+ /*admin=*/ null,
+ /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
+ MASTER_CLEAR},
+ USES_POLICY_WIPE_DATA,
+ adminPackage,
+ factoryReset ? UserHandle.USER_ALL :
+ getAffectedUser(calledOnParentInstance));
+ wipeDevice = true;
} else {
- // Explicit behaviour
- if (factoryReset) {
- EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
- /*admin=*/ null,
- /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
- MASTER_CLEAR},
- USES_POLICY_WIPE_DATA,
- adminPackage,
- factoryReset ? UserHandle.USER_ALL :
- getAffectedUser(calledOnParentInstance));
- wipeDevice = true;
- } else {
+ mInjector.binderWithCleanCallingIdentity(() -> {
Preconditions.checkCallAuthorization(!isSystemUser,
"User %s is a system user and cannot be removed", userId);
boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
@@ -7871,9 +7874,11 @@
"Removing user %s would leave the device without any active users. "
+ "Consider factory resetting the device instead.",
userId);
- wipeDevice = false;
- }
+ });
+ wipeDevice = false;
}
+ }
+ mInjector.binderWithCleanCallingIdentity(() -> {
if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
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/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 582685c..f4238f6 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/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 51e521d..206af5b 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;
@@ -106,6 +108,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.List;
/**
* Tests for the {@link WallpaperManagerService} class.
@@ -172,12 +175,12 @@
sImageWallpaperComponentName = ComponentName.unflattenFromString(
sContext.getResources().getString(R.string.image_wallpaper_component));
// Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
- sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
+ sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
if (sDefaultWallpaperComponent == null) {
sDefaultWallpaperComponent = sImageWallpaperComponentName;
doReturn(sImageWallpaperComponentName).when(() ->
- WallpaperManager.getCmfDefaultWallpaperComponent(any()));
+ WallpaperManager.getDefaultWallpaperComponent(any()));
} else {
sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
}
@@ -262,6 +265,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
@@ -405,10 +427,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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2a0c745..cebc540 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -587,6 +587,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);
@@ -1929,8 +1931,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/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index b59f027..1126726 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1346,6 +1346,16 @@
activity1.setVisible(false);
abortTransition.abort();
assertTrue(activity1.isVisible());
+
+ // The mLaunchTaskBehind flag of an invisible initializing activity should not be cleared.
+ final Transition noChangeTransition = controller.createTransition(TRANSIT_OPEN);
+ noChangeTransition.collect(activity1);
+ activity1.setVisibleRequested(false);
+ activity1.setState(ActivityRecord.State.INITIALIZING, "test");
+ activity1.mLaunchTaskBehind = true;
+ mWm.mSyncEngine.abort(noChangeTransition.getSyncId());
+ noChangeTransition.finishTransition();
+ assertTrue(activity1.mLaunchTaskBehind);
}
@Test
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1888943..5b1c6b1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10318,7 +10318,7 @@
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
sDefaults.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, 300);
- sDefaults.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, 0);
+ sDefaults.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, -1);
// Default wifi configurations.
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
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
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4123f80..960b57c 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -118,6 +118,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnManagementServiceTest {
+ private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
private static final String TEST_PACKAGE_NAME =
VcnManagementServiceTest.class.getPackage().getName();
private static final String TEST_PACKAGE_NAME_2 = "TEST_PKG_2";
@@ -177,6 +178,7 @@
0 /* carrierId */,
0 /* profileClass */);
+ private final Context mMockContextWithoutAttributionTag = mock(Context.class);
private final Context mMockContext = mock(Context.class);
private final VcnManagementService.Dependencies mMockDeps =
mock(VcnManagementService.Dependencies.class);
@@ -202,6 +204,10 @@
private final IBinder mMockIBinder = mock(IBinder.class);
public VcnManagementServiceTest() throws Exception {
+ doReturn(mMockContext)
+ .when(mMockContextWithoutAttributionTag)
+ .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
+
setupSystemService(
mMockContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
setupSystemService(
@@ -249,7 +255,7 @@
doReturn(bundle).when(mConfigReadWriteHelper).readFromDisk();
setupMockedCarrierPrivilege(true);
- mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
+ mVcnMgmtSvc = new VcnManagementService(mMockContextWithoutAttributionTag, mMockDeps);
setupActiveSubscription(TEST_UUID_1);
doReturn(mMockIBinder).when(mMockPolicyListener).asBinder();