Merge "Remove isWindowToken check from requestScrollCapture"
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 9604466..754c4e9 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -77,6 +77,7 @@
"android.hardware.vibrator-V1.3-java",
"framework-protos",
],
+ high_mem: true, // Lots of sources => high memory use, see b/170701554
installable: false,
annotations_enabled: true,
previous_api: ":android.api.public.latest",
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 6c14233..34e82b0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -339,6 +339,11 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
boolean apiQuotaScheduleUpdated = false;
boolean concurrencyUpdated = false;
+ for (int controller = 0; controller < mControllers.size(); controller++) {
+ final StateController sc = mControllers.get(controller);
+ sc.prepareForUpdatedConstantsLocked();
+ }
+
synchronized (mLock) {
for (String name : properties.getKeyset()) {
if (name == null) {
@@ -384,6 +389,11 @@
&& !concurrencyUpdated) {
mConstants.updateConcurrencyConstantsLocked();
concurrencyUpdated = true;
+ } else {
+ for (int ctrlr = 0; ctrlr < mControllers.size(); ctrlr++) {
+ final StateController sc = mControllers.get(ctrlr);
+ sc.processConstantLocked(properties, name);
+ }
}
break;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index c7cc2f0..00dbb82 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
@@ -1388,6 +1388,11 @@
}
if (isReady()) {
sb.append(" READY");
+ } else {
+ sb.append(" satisfied:0x").append(Integer.toHexString(satisfiedConstraints));
+ sb.append(" unsatisfied:0x").append(Integer.toHexString(
+ (satisfiedConstraints & mRequiredConstraintsOfInterest)
+ ^ mRequiredConstraintsOfInterest));
}
sb.append("}");
return sb.toString();
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 c06e19c..b7ace70 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
@@ -37,12 +37,9 @@
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Handler;
@@ -50,10 +47,9 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -494,7 +490,7 @@
mChargeTracker.startTracking();
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- mQcConstants = new QcConstants(mHandler);
+ mQcConstants = new QcConstants();
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
mContext.registerReceiverAsUser(mPackageAddedReceiver, UserHandle.ALL, filter, null, null);
@@ -513,11 +509,6 @@
}
@Override
- public void onSystemServicesReady() {
- mQcConstants.start(mContext.getContentResolver());
- }
-
- @Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
final int userId = jobStatus.getSourceUserId();
final String pkgName = jobStatus.getSourcePackageName();
@@ -2028,38 +2019,109 @@
}
}
- @VisibleForTesting
- class QcConstants extends ContentObserver {
- private ContentResolver mResolver;
- private final KeyValueListParser mParser = new KeyValueListParser(',');
+ @Override
+ public void prepareForUpdatedConstantsLocked() {
+ mQcConstants.mShouldReevaluateConstraints = false;
+ mQcConstants.mRateLimitingConstantsUpdated = false;
+ mQcConstants.mExecutionPeriodConstantsUpdated = false;
+ }
- private static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = "allowed_time_per_period_ms";
- private static final String KEY_IN_QUOTA_BUFFER_MS = "in_quota_buffer_ms";
- private static final String KEY_WINDOW_SIZE_ACTIVE_MS = "window_size_active_ms";
- private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms";
- private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms";
- private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms";
- private static final String KEY_WINDOW_SIZE_RESTRICTED_MS = "window_size_restricted_ms";
- private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms";
- private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active";
- private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
- private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
- private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
- private static final String KEY_MAX_JOB_COUNT_RESTRICTED = "max_job_count_restricted";
- private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
- private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
- "max_job_count_per_rate_limiting_window";
- private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
- private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
- private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
- private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
- private static final String KEY_MAX_SESSION_COUNT_RESTRICTED =
- "max_session_count_restricted";
- private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
- "max_session_count_per_rate_limiting_window";
- private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
- "timing_session_coalescing_duration_ms";
- private static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = "min_quota_check_delay_ms";
+ @Override
+ public void processConstantLocked(DeviceConfig.Properties properties, String key) {
+ mQcConstants.processConstantLocked(properties, key);
+ }
+
+ @Override
+ public void onConstantsUpdatedLocked() {
+ if (mQcConstants.mShouldReevaluateConstraints) {
+ // Update job bookkeeping out of band.
+ JobSchedulerBackgroundThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ invalidateAllExecutionStatsLocked();
+ maybeUpdateAllConstraintsLocked();
+ }
+ });
+ }
+ }
+
+ @VisibleForTesting
+ class QcConstants {
+ private boolean mShouldReevaluateConstraints = false;
+ private boolean mRateLimitingConstantsUpdated = false;
+ private boolean mExecutionPeriodConstantsUpdated = false;
+
+ /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
+ private static final String QC_CONSTANT_PREFIX = "qc_";
+
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_ms";
+ @VisibleForTesting
+ static final String KEY_IN_QUOTA_BUFFER_MS =
+ QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
+ @VisibleForTesting
+ static final String KEY_WINDOW_SIZE_ACTIVE_MS =
+ QC_CONSTANT_PREFIX + "window_size_active_ms";
+ @VisibleForTesting
+ static final String KEY_WINDOW_SIZE_WORKING_MS =
+ QC_CONSTANT_PREFIX + "window_size_working_ms";
+ @VisibleForTesting
+ static final String KEY_WINDOW_SIZE_FREQUENT_MS =
+ QC_CONSTANT_PREFIX + "window_size_frequent_ms";
+ @VisibleForTesting
+ static final String KEY_WINDOW_SIZE_RARE_MS =
+ QC_CONSTANT_PREFIX + "window_size_rare_ms";
+ @VisibleForTesting
+ static final String KEY_WINDOW_SIZE_RESTRICTED_MS =
+ QC_CONSTANT_PREFIX + "window_size_restricted_ms";
+ @VisibleForTesting
+ static final String KEY_MAX_EXECUTION_TIME_MS =
+ QC_CONSTANT_PREFIX + "max_execution_time_ms";
+ @VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_ACTIVE =
+ QC_CONSTANT_PREFIX + "max_job_count_active";
+ @VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_WORKING =
+ QC_CONSTANT_PREFIX + "max_job_count_working";
+ @VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_FREQUENT =
+ QC_CONSTANT_PREFIX + "max_job_count_frequent";
+ @VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_RARE =
+ QC_CONSTANT_PREFIX + "max_job_count_rare";
+ @VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_RESTRICTED =
+ QC_CONSTANT_PREFIX + "max_job_count_restricted";
+ @VisibleForTesting
+ static final String KEY_RATE_LIMITING_WINDOW_MS =
+ QC_CONSTANT_PREFIX + "rate_limiting_window_ms";
+ @VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+ QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window";
+ @VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_ACTIVE =
+ QC_CONSTANT_PREFIX + "max_session_count_active";
+ @VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_WORKING =
+ QC_CONSTANT_PREFIX + "max_session_count_working";
+ @VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_FREQUENT =
+ QC_CONSTANT_PREFIX + "max_session_count_frequent";
+ @VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_RARE =
+ QC_CONSTANT_PREFIX + "max_session_count_rare";
+ @VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_RESTRICTED =
+ QC_CONSTANT_PREFIX + "max_session_count_restricted";
+ @VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+ QC_CONSTANT_PREFIX + "max_session_count_per_rate_limiting_window";
+ @VisibleForTesting
+ static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
+ QC_CONSTANT_PREFIX + "timing_session_coalescing_duration_ms";
+ @VisibleForTesting
+ static final String KEY_MIN_QUOTA_CHECK_DELAY_MS =
+ QC_CONSTANT_PREFIX + "min_quota_check_delay_ms";
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
10 * 60 * 1000L; // 10 minutes
@@ -2260,238 +2322,273 @@
/** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
- QcConstants(Handler handler) {
- super(handler);
- }
+ public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {
+ switch (key) {
+ case KEY_ALLOWED_TIME_PER_PERIOD_MS:
+ case KEY_IN_QUOTA_BUFFER_MS:
+ case KEY_MAX_EXECUTION_TIME_MS:
+ case KEY_WINDOW_SIZE_ACTIVE_MS:
+ case KEY_WINDOW_SIZE_WORKING_MS:
+ case KEY_WINDOW_SIZE_FREQUENT_MS:
+ case KEY_WINDOW_SIZE_RARE_MS:
+ case KEY_WINDOW_SIZE_RESTRICTED_MS:
+ updateExecutionPeriodConstantsLocked();
+ break;
- private void start(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this);
- onChange(true, null);
- }
+ case KEY_RATE_LIMITING_WINDOW_MS:
+ case KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW:
+ case KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW:
+ updateRateLimitingConstantsLocked();
+ break;
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- final String constants = Settings.Global.getString(
- mResolver, Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS);
-
- try {
- mParser.setString(constants);
- } catch (Exception e) {
- // Failed to parse the settings string, log this and move on with defaults.
- Slog.e(TAG, "Bad jobscheduler quota controller settings", e);
+ case KEY_MAX_JOB_COUNT_ACTIVE:
+ MAX_JOB_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_ACTIVE);
+ int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
+ if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
+ mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_JOB_COUNT_WORKING:
+ MAX_JOB_COUNT_WORKING = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_WORKING);
+ int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT,
+ MAX_JOB_COUNT_WORKING);
+ if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
+ mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_JOB_COUNT_FREQUENT:
+ MAX_JOB_COUNT_FREQUENT = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_FREQUENT);
+ int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT,
+ MAX_JOB_COUNT_FREQUENT);
+ if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
+ mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_JOB_COUNT_RARE:
+ MAX_JOB_COUNT_RARE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_RARE);
+ int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
+ if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
+ mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_JOB_COUNT_RESTRICTED:
+ MAX_JOB_COUNT_RESTRICTED =
+ properties.getInt(key, DEFAULT_MAX_JOB_COUNT_RESTRICTED);
+ int newRestrictedMaxJobCount =
+ Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RESTRICTED);
+ if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) {
+ mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_SESSION_COUNT_ACTIVE:
+ MAX_SESSION_COUNT_ACTIVE =
+ properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
+ int newActiveMaxSessionCount =
+ Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE);
+ if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) {
+ mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_SESSION_COUNT_WORKING:
+ MAX_SESSION_COUNT_WORKING =
+ properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_WORKING);
+ int newWorkingMaxSessionCount =
+ Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING);
+ if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) {
+ mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_SESSION_COUNT_FREQUENT:
+ MAX_SESSION_COUNT_FREQUENT =
+ properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
+ int newFrequentMaxSessionCount =
+ Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT);
+ if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) {
+ mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_SESSION_COUNT_RARE:
+ MAX_SESSION_COUNT_RARE = properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_RARE);
+ int newRareMaxSessionCount =
+ Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE);
+ if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) {
+ mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MAX_SESSION_COUNT_RESTRICTED:
+ MAX_SESSION_COUNT_RESTRICTED =
+ properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_RESTRICTED);
+ int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED);
+ if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) {
+ mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_TIMING_SESSION_COALESCING_DURATION_MS:
+ TIMING_SESSION_COALESCING_DURATION_MS =
+ properties.getLong(key, DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
+ long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
+ Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
+ if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
+ mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MIN_QUOTA_CHECK_DELAY_MS:
+ MIN_QUOTA_CHECK_DELAY_MS =
+ properties.getLong(key, DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
+ // We don't need to re-evaluate execution stats or constraint status for this.
+ // Limit the delay to the range [0, 15] minutes.
+ mInQuotaAlarmListener.setMinQuotaCheckDelayMs(
+ Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS)));
+ break;
}
-
- ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis(
- KEY_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
- IN_QUOTA_BUFFER_MS = mParser.getDurationMillis(
- KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS);
- WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS);
- WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
- WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS);
- WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS);
- WINDOW_SIZE_RESTRICTED_MS = mParser.getDurationMillis(
- KEY_WINDOW_SIZE_RESTRICTED_MS, DEFAULT_WINDOW_SIZE_RESTRICTED_MS);
- MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
- KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS);
- MAX_JOB_COUNT_ACTIVE = mParser.getInt(
- KEY_MAX_JOB_COUNT_ACTIVE, DEFAULT_MAX_JOB_COUNT_ACTIVE);
- MAX_JOB_COUNT_WORKING = mParser.getInt(
- KEY_MAX_JOB_COUNT_WORKING, DEFAULT_MAX_JOB_COUNT_WORKING);
- MAX_JOB_COUNT_FREQUENT = mParser.getInt(
- KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
- MAX_JOB_COUNT_RARE = mParser.getInt(
- KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
- MAX_JOB_COUNT_RESTRICTED = mParser.getInt(
- KEY_MAX_JOB_COUNT_RESTRICTED, DEFAULT_MAX_JOB_COUNT_RESTRICTED);
- RATE_LIMITING_WINDOW_MS = mParser.getLong(
- KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
- MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
- KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
- DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
- MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
- KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
- MAX_SESSION_COUNT_WORKING = mParser.getInt(
- KEY_MAX_SESSION_COUNT_WORKING, DEFAULT_MAX_SESSION_COUNT_WORKING);
- MAX_SESSION_COUNT_FREQUENT = mParser.getInt(
- KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
- MAX_SESSION_COUNT_RARE = mParser.getInt(
- KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
- MAX_SESSION_COUNT_RESTRICTED = mParser.getInt(
- KEY_MAX_SESSION_COUNT_RESTRICTED, DEFAULT_MAX_SESSION_COUNT_RESTRICTED);
- MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
- KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
- DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
- TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
- KEY_TIMING_SESSION_COALESCING_DURATION_MS,
- DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
- MIN_QUOTA_CHECK_DELAY_MS = mParser.getDurationMillis(KEY_MIN_QUOTA_CHECK_DELAY_MS,
- DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
-
- updateConstants();
}
- @VisibleForTesting
- void updateConstants() {
- synchronized (mLock) {
- boolean changed = false;
+ private void updateExecutionPeriodConstantsLocked() {
+ if (mExecutionPeriodConstantsUpdated) {
+ return;
+ }
+ mExecutionPeriodConstantsUpdated = true;
- long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
- Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
- if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
- mMaxExecutionTimeMs = newMaxExecutionTimeMs;
- mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
- changed = true;
- }
- long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
- Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
- if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
- mAllowedTimePerPeriodMs = newAllowedTimeMs;
- mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
- changed = true;
- }
- // Make sure quota buffer is non-negative, not greater than allowed time per period,
- // and no more than 5 minutes.
- long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs,
- Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS)));
- if (mQuotaBufferMs != newQuotaBufferMs) {
- mQuotaBufferMs = newQuotaBufferMs;
- mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
- mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
- changed = true;
- }
- long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
- if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
- mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
- changed = true;
- }
- long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
- if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
- mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
- changed = true;
- }
- long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
- if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
- mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
- changed = true;
- }
- long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
- if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
- mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
- changed = true;
- }
- // Fit in the range [allowed time (10 mins), 1 week].
- long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs,
- Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS));
- if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) {
- mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
- changed = true;
- }
- long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
- Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
- if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
- mRateLimitingWindowMs = newRateLimitingWindowMs;
- changed = true;
- }
- int newMaxJobCountPerRateLimitingWindow = Math.max(
- MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
- if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
- mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
- changed = true;
- }
- int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
- if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
- mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
- changed = true;
- }
- int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
- if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
- mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
- changed = true;
- }
- int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
- if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
- mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
- changed = true;
- }
- int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
- if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
- mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
- changed = true;
- }
- int newRestrictedMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT,
- MAX_JOB_COUNT_RESTRICTED);
- if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) {
- mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount;
- changed = true;
- }
- int newMaxSessionCountPerRateLimitPeriod = Math.max(
- MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
- MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
- if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
- mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
- changed = true;
- }
- int newActiveMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE);
- if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) {
- mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount;
- changed = true;
- }
- int newWorkingMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING);
- if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) {
- mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount;
- changed = true;
- }
- int newFrequentMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT);
- if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) {
- mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount;
- changed = true;
- }
- int newRareMaxSessionCount =
- Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE);
- if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) {
- mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
- changed = true;
- }
- int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED);
- if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) {
- mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount;
- changed = true;
- }
- long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
- Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
- if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
- mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs;
- changed = true;
- }
- // Don't set changed to true for this one since we don't need to re-evaluate
- // execution stats or constraint status. Limit the delay to the range [0, 15]
- // minutes.
- mInQuotaAlarmListener.setMinQuotaCheckDelayMs(
- Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS)));
+ // Query the values as an atomic set.
+ final DeviceConfig.Properties properties = DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS,
+ KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
+ KEY_WINDOW_SIZE_WORKING_MS,
+ KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS,
+ KEY_WINDOW_SIZE_RESTRICTED_MS);
+ ALLOWED_TIME_PER_PERIOD_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
+ IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
+ DEFAULT_IN_QUOTA_BUFFER_MS);
+ MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
+ DEFAULT_MAX_EXECUTION_TIME_MS);
+ WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
+ DEFAULT_WINDOW_SIZE_ACTIVE_MS);
+ WINDOW_SIZE_WORKING_MS =
+ properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
+ WINDOW_SIZE_FREQUENT_MS =
+ properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS,
+ DEFAULT_WINDOW_SIZE_FREQUENT_MS);
+ WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS,
+ DEFAULT_WINDOW_SIZE_RARE_MS);
+ WINDOW_SIZE_RESTRICTED_MS =
+ properties.getLong(KEY_WINDOW_SIZE_RESTRICTED_MS,
+ DEFAULT_WINDOW_SIZE_RESTRICTED_MS);
- if (changed) {
- // Update job bookkeeping out of band.
- JobSchedulerBackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- invalidateAllExecutionStatsLocked();
- maybeUpdateAllConstraintsLocked();
- }
- });
- }
+ long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
+ Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
+ if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
+ mMaxExecutionTimeMs = newMaxExecutionTimeMs;
+ mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
+ if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
+ mAllowedTimePerPeriodMs = newAllowedTimeMs;
+ mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+ mShouldReevaluateConstraints = true;
+ }
+ // Make sure quota buffer is non-negative, not greater than allowed time per period,
+ // and no more than 5 minutes.
+ long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs,
+ Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS)));
+ if (mQuotaBufferMs != newQuotaBufferMs) {
+ mQuotaBufferMs = newQuotaBufferMs;
+ mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+ mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
+ if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
+ mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
+ if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
+ mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
+ if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
+ mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
+ if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
+ mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
+ mShouldReevaluateConstraints = true;
+ }
+ // Fit in the range [allowed time (10 mins), 1 week].
+ long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS));
+ if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) {
+ mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
+ mShouldReevaluateConstraints = true;
+ }
+ }
+
+ private void updateRateLimitingConstantsLocked() {
+ if (mRateLimitingConstantsUpdated) {
+ return;
+ }
+ mRateLimitingConstantsUpdated = true;
+
+ // Query the values as an atomic set.
+ final DeviceConfig.Properties properties = DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_RATE_LIMITING_WINDOW_MS, KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
+
+ RATE_LIMITING_WINDOW_MS =
+ properties.getLong(KEY_RATE_LIMITING_WINDOW_MS,
+ DEFAULT_RATE_LIMITING_WINDOW_MS);
+
+ MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+ properties.getInt(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
+
+ MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+ properties.getInt(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
+
+ long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
+ Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
+ if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
+ mRateLimitingWindowMs = newRateLimitingWindowMs;
+ mShouldReevaluateConstraints = true;
+ }
+ int newMaxJobCountPerRateLimitingWindow = Math.max(
+ MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
+ if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
+ mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
+ mShouldReevaluateConstraints = true;
+ }
+ int newMaxSessionCountPerRateLimitPeriod = Math.max(
+ MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
+ if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
+ mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
+ mShouldReevaluateConstraints = true;
}
}
@@ -2634,6 +2731,11 @@
}
@VisibleForTesting
+ long getMinQuotaCheckDelayMs() {
+ return mInQuotaAlarmListener.mMinQuotaCheckDelayMs;
+ }
+
+ @VisibleForTesting
long getRateLimitingWindowMs() {
return mRateLimitingWindowMs;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 71c7599..56b3090 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -18,7 +18,9 @@
import static com.android.server.job.JobSchedulerService.DEBUG;
+import android.annotation.NonNull;
import android.content.Context;
+import android.provider.DeviceConfig;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -84,6 +86,13 @@
public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
}
+ /** Notice that updated configuration constants are about to be read. */
+ public void prepareForUpdatedConstantsLocked() {}
+
+ /** Process the specified constant and update internal constants if relevant. */
+ public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {}
+
/**
* Called when the JobScheduler.Constants are updated.
*/
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 813631e..b3c9a9a 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -35,7 +35,6 @@
libs: [
"framework_media_annotation",
],
-
static_libs: [
"exoplayer2-extractor"
],
@@ -111,10 +110,33 @@
impl_library_visibility: ["//frameworks/av/apex:__subpackages__"],
}
-
java_library {
name: "framework_media_annotation",
srcs: [":framework-media-annotation-srcs"],
installable: false,
sdk_version: "core_current",
}
+
+cc_library_shared {
+ name: "libmediaparser-jni",
+ srcs: [
+ "jni/android_media_MediaParserJNI.cpp",
+ ],
+ header_libs: ["jni_headers"],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libmediametrics",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wunreachable-code",
+ "-Wunused",
+ ],
+ apex_available: [
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
+}
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index e4b5d19..045b413 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -75,6 +75,8 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Function;
/**
* Parses media container formats and extracts contained media samples and metadata.
@@ -882,6 +884,7 @@
// Private constants.
private static final String TAG = "MediaParser";
+ private static final String JNI_LIBRARY_NAME = "mediaparser-jni";
private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME;
private static final Map<String, Class> EXPECTED_TYPE_BY_PARAMETER_NAME;
private static final String TS_MODE_SINGLE_PMT = "single_pmt";
@@ -889,6 +892,14 @@
private static final String TS_MODE_HLS = "hls";
private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ private static final String MEDIAMETRICS_ELEMENT_SEPARATOR = "|";
+ private static final int MEDIAMETRICS_MAX_STRING_SIZE = 200;
+ private static final int MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH;
+ /**
+ * Intentional error introduced to reported metrics to prevent identification of the parsed
+ * media. Note: Increasing this value may cause older hostside CTS tests to fail.
+ */
+ private static final float MEDIAMETRICS_DITHER = .02f;
@IntDef(
value = {
@@ -920,7 +931,7 @@
@NonNull @ParserName String name, @NonNull OutputConsumer outputConsumer) {
String[] nameAsArray = new String[] {name};
assertValidNames(nameAsArray);
- return new MediaParser(outputConsumer, /* sniff= */ false, name);
+ return new MediaParser(outputConsumer, /* createdByName= */ true, name);
}
/**
@@ -940,7 +951,7 @@
if (parserNames.length == 0) {
parserNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]);
}
- return new MediaParser(outputConsumer, /* sniff= */ true, parserNames);
+ return new MediaParser(outputConsumer, /* createdByName= */ false, parserNames);
}
// Misc static methods.
@@ -1052,6 +1063,14 @@
private long mPendingSeekPosition;
private long mPendingSeekTimeMicros;
private boolean mLoggedSchemeInitDataCreationException;
+ private boolean mReleased;
+
+ // MediaMetrics fields.
+ private final boolean mCreatedByName;
+ private final SparseArray<Format> mTrackFormats;
+ private String mLastObservedExceptionName;
+ private long mDurationMillis;
+ private long mResourceByteCount;
// Public methods.
@@ -1166,11 +1185,16 @@
if (mExtractorInput == null) {
// TODO: For efficiency, the same implementation should be used, by providing a
// clearBuffers() method, or similar.
+ long resourceLength = seekableInputReader.getLength();
+ if (resourceLength == -1) {
+ mResourceByteCount = -1;
+ }
+ if (mResourceByteCount != -1) {
+ mResourceByteCount += resourceLength;
+ }
mExtractorInput =
new DefaultExtractorInput(
- mExoDataReader,
- seekableInputReader.getPosition(),
- seekableInputReader.getLength());
+ mExoDataReader, seekableInputReader.getPosition(), resourceLength);
}
mExoDataReader.mInputReader = seekableInputReader;
@@ -1195,7 +1219,10 @@
}
}
if (mExtractor == null) {
- throw UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
+ UnrecognizedInputFormatException exception =
+ UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
+ mLastObservedExceptionName = exception.getClass().getName();
+ throw exception;
}
return true;
}
@@ -1223,8 +1250,13 @@
int result;
try {
result = mExtractor.read(mExtractorInput, mPositionHolder);
- } catch (ParserException e) {
- throw new ParsingException(e);
+ } catch (Exception e) {
+ mLastObservedExceptionName = e.getClass().getName();
+ if (e instanceof ParserException) {
+ throw new ParsingException((ParserException) e);
+ } else {
+ throw e;
+ }
}
if (result == Extractor.RESULT_END_OF_INPUT) {
mExtractorInput = null;
@@ -1264,21 +1296,64 @@
* invoked.
*/
public void release() {
- // TODO: Dump media metrics here.
mExtractorInput = null;
mExtractor = null;
+ if (mReleased) {
+ // Nothing to do.
+ return;
+ }
+ mReleased = true;
+
+ String trackMimeTypes = buildMediaMetricsString(format -> format.sampleMimeType);
+ String trackCodecs = buildMediaMetricsString(format -> format.codecs);
+ int videoWidth = -1;
+ int videoHeight = -1;
+ for (int i = 0; i < mTrackFormats.size(); i++) {
+ Format format = mTrackFormats.valueAt(i);
+ if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) {
+ videoWidth = format.width;
+ videoHeight = format.height;
+ break;
+ }
+ }
+
+ String alteredParameters =
+ String.join(
+ MEDIAMETRICS_ELEMENT_SEPARATOR,
+ mParserParameters.keySet().toArray(new String[0]));
+ alteredParameters =
+ alteredParameters.substring(
+ 0,
+ Math.min(
+ alteredParameters.length(),
+ MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
+
+ nativeSubmitMetrics(
+ mParserName,
+ mCreatedByName,
+ String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
+ mLastObservedExceptionName,
+ addDither(mResourceByteCount),
+ addDither(mDurationMillis),
+ trackMimeTypes,
+ trackCodecs,
+ alteredParameters,
+ videoWidth,
+ videoHeight);
}
// Private methods.
- private MediaParser(OutputConsumer outputConsumer, boolean sniff, String... parserNamesPool) {
+ private MediaParser(
+ OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
throw new UnsupportedOperationException("Android version must be R or greater.");
}
mParserParameters = new HashMap<>();
mOutputConsumer = outputConsumer;
mParserNamesPool = parserNamesPool;
- mParserName = sniff ? PARSER_NAME_UNKNOWN : parserNamesPool[0];
+ mCreatedByName = createdByName;
+ mParserName = createdByName ? parserNamesPool[0] : PARSER_NAME_UNKNOWN;
mPositionHolder = new PositionHolder();
mExoDataReader = new InputReadingDataReader();
removePendingSeek();
@@ -1286,6 +1361,24 @@
mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter();
mSchemeInitDataConstructor = getSchemeInitDataConstructor();
mMuxedCaptionFormats = new ArrayList<>();
+
+ // MediaMetrics.
+ mTrackFormats = new SparseArray<>();
+ mLastObservedExceptionName = "";
+ mDurationMillis = -1;
+ }
+
+ private String buildMediaMetricsString(Function<Format, String> formatFieldGetter) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < mTrackFormats.size(); i++) {
+ if (i > 0) {
+ stringBuilder.append(MEDIAMETRICS_ELEMENT_SEPARATOR);
+ }
+ String fieldValue = formatFieldGetter.apply(mTrackFormats.valueAt(i));
+ stringBuilder.append(fieldValue != null ? fieldValue : "");
+ }
+ return stringBuilder.substring(
+ 0, Math.min(stringBuilder.length(), MEDIAMETRICS_MAX_STRING_SIZE));
}
private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) {
@@ -1528,6 +1621,10 @@
@Override
public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
+ long durationUs = exoplayerSeekMap.getDurationUs();
+ if (durationUs != C.TIME_UNSET) {
+ mDurationMillis = C.usToMs(durationUs);
+ }
if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) {
ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap;
MediaFormat mediaFormat = new MediaFormat();
@@ -1575,6 +1672,7 @@
@Override
public void format(Format format) {
+ mTrackFormats.put(mTrackIndex, format);
mOutputConsumer.onTrackDataFound(
mTrackIndex,
new TrackData(
@@ -2031,6 +2129,20 @@
return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position);
}
+ /**
+ * Introduces random error to the given metric value in order to prevent the identification of
+ * the parsed media.
+ */
+ private static long addDither(long value) {
+ // Generate a random in [0, 1].
+ double randomDither = ThreadLocalRandom.current().nextFloat();
+ // Clamp the random number to [0, 2 * MEDIAMETRICS_DITHER].
+ randomDither *= 2 * MEDIAMETRICS_DITHER;
+ // Translate the random number to [1 - MEDIAMETRICS_DITHER, 1 + MEDIAMETRICS_DITHER].
+ randomDither += 1 - MEDIAMETRICS_DITHER;
+ return value != -1 ? (long) (value * randomDither) : -1;
+ }
+
private static void assertValidNames(@NonNull String[] names) {
for (String name : names) {
if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) {
@@ -2070,9 +2182,26 @@
}
}
+ // Native methods.
+
+ private native void nativeSubmitMetrics(
+ String parserName,
+ boolean createdByName,
+ String parserPool,
+ String lastObservedExceptionName,
+ long resourceByteCount,
+ long durationMillis,
+ String trackMimeTypes,
+ String trackCodecs,
+ String alteredParameters,
+ int videoWidth,
+ int videoHeight);
+
// Static initialization.
static {
+ System.loadLibrary(JNI_LIBRARY_NAME);
+
// Using a LinkedHashMap to keep the insertion order when iterating over the keys.
LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>();
// Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering,
@@ -2125,6 +2254,15 @@
// We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters
// instead. Checking that the value is a List is insufficient to catch wrong parameter
// value types.
+ int sumOfParameterNameLengths =
+ expectedTypeByParameterName.keySet().stream()
+ .map(String::length)
+ .reduce(0, Integer::sum);
+ sumOfParameterNameLengths += PARAMETER_EXPOSE_CAPTION_FORMATS.length();
+ // Add space for any required separators.
+ MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH =
+ sumOfParameterNameLengths + expectedTypeByParameterName.size();
+
EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
}
}
diff --git a/apex/media/framework/jni/android_media_MediaParserJNI.cpp b/apex/media/framework/jni/android_media_MediaParserJNI.cpp
new file mode 100644
index 0000000..7fc4628
--- /dev/null
+++ b/apex/media/framework/jni/android_media_MediaParserJNI.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <media/MediaMetrics.h>
+
+#define JNI_FUNCTION(RETURN_TYPE, NAME, ...) \
+ extern "C" { \
+ JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
+ ##__VA_ARGS__); \
+ } \
+ JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
+ ##__VA_ARGS__)
+
+namespace {
+
+constexpr char kMediaMetricsKey[] = "mediaparser";
+
+constexpr char kAttributeParserName[] = "android.media.mediaparser.parserName";
+constexpr char kAttributeCreatedByName[] = "android.media.mediaparser.createdByName";
+constexpr char kAttributeParserPool[] = "android.media.mediaparser.parserPool";
+constexpr char kAttributeLastException[] = "android.media.mediaparser.lastException";
+constexpr char kAttributeResourceByteCount[] = "android.media.mediaparser.resourceByteCount";
+constexpr char kAttributeDurationMillis[] = "android.media.mediaparser.durationMillis";
+constexpr char kAttributeTrackMimeTypes[] = "android.media.mediaparser.trackMimeTypes";
+constexpr char kAttributeTrackCodecs[] = "android.media.mediaparser.trackCodecs";
+constexpr char kAttributeAlteredParameters[] = "android.media.mediaparser.alteredParameters";
+constexpr char kAttributeVideoWidth[] = "android.media.mediaparser.videoWidth";
+constexpr char kAttributeVideoHeight[] = "android.media.mediaparser.videoHeight";
+
+// Util class to handle string resource management.
+class JstringHandle {
+public:
+ JstringHandle(JNIEnv* env, jstring value) : mEnv(env), mJstringValue(value) {
+ mCstringValue = env->GetStringUTFChars(value, /* isCopy= */ nullptr);
+ }
+
+ ~JstringHandle() {
+ if (mCstringValue != nullptr) {
+ mEnv->ReleaseStringUTFChars(mJstringValue, mCstringValue);
+ }
+ }
+
+ [[nodiscard]] const char* value() const {
+ return mCstringValue != nullptr ? mCstringValue : "";
+ }
+
+ JNIEnv* mEnv;
+ jstring mJstringValue;
+ const char* mCstringValue;
+};
+
+} // namespace
+
+JNI_FUNCTION(void, nativeSubmitMetrics, jstring parserNameJstring, jboolean createdByName,
+ jstring parserPoolJstring, jstring lastExceptionJstring, jlong resourceByteCount,
+ jlong durationMillis, jstring trackMimeTypesJstring, jstring trackCodecsJstring,
+ jstring alteredParameters, jint videoWidth, jint videoHeight) {
+ mediametrics_handle_t item(mediametrics_create(kMediaMetricsKey));
+ mediametrics_setCString(item, kAttributeParserName,
+ JstringHandle(env, parserNameJstring).value());
+ mediametrics_setInt32(item, kAttributeCreatedByName, createdByName ? 1 : 0);
+ mediametrics_setCString(item, kAttributeParserPool,
+ JstringHandle(env, parserPoolJstring).value());
+ mediametrics_setCString(item, kAttributeLastException,
+ JstringHandle(env, lastExceptionJstring).value());
+ mediametrics_setInt64(item, kAttributeResourceByteCount, resourceByteCount);
+ mediametrics_setInt64(item, kAttributeDurationMillis, durationMillis);
+ mediametrics_setCString(item, kAttributeTrackMimeTypes,
+ JstringHandle(env, trackMimeTypesJstring).value());
+ mediametrics_setCString(item, kAttributeTrackCodecs,
+ JstringHandle(env, trackCodecsJstring).value());
+ mediametrics_setCString(item, kAttributeAlteredParameters,
+ JstringHandle(env, alteredParameters).value());
+ mediametrics_setInt32(item, kAttributeVideoWidth, videoWidth);
+ mediametrics_setInt32(item, kAttributeVideoHeight, videoHeight);
+ mediametrics_selfRecord(item);
+ mediametrics_delete(item);
+}
diff --git a/api/current.txt b/api/current.txt
index 1581960..b880f85 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -97,6 +97,7 @@
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
+ field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
@@ -17864,6 +17865,7 @@
public class CaptureResult extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureResult.Key<?>> {
method @Nullable public <T> T get(android.hardware.camera2.CaptureResult.Key<T>);
+ method @NonNull public String getCameraId();
method public long getFrameNumber();
method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getKeys();
method @NonNull public android.hardware.camera2.CaptureRequest getRequest();
@@ -38882,6 +38884,9 @@
ctor public CallLog.Calls();
method public static String getLastOutgoingCall(android.content.Context);
field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
+ field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L
+ field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L
+ field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final String BLOCK_REASON = "block_reason";
field public static final int BLOCK_REASON_BLOCKED_NUMBER = 3; // 0x3
@@ -38927,6 +38932,8 @@
field public static final String IS_READ = "is_read";
field public static final String LAST_MODIFIED = "last_modified";
field public static final String LIMIT_PARAM_KEY = "limit";
+ field public static final String MISSED_REASON = "missed_reason";
+ field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L
field public static final int MISSED_TYPE = 3; // 0x3
field public static final String NEW = "new";
field public static final String NUMBER = "number";
@@ -38943,6 +38950,13 @@
field public static final int REJECTED_TYPE = 5; // 0x5
field public static final String TRANSCRIPTION = "transcription";
field public static final String TYPE = "type";
+ field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L
+ field public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 2097152L; // 0x200000L
+ field public static final long USER_MISSED_DND_MODE = 262144L; // 0x40000L
+ field public static final long USER_MISSED_LOW_RING_VOLUME = 524288L; // 0x80000L
+ field public static final long USER_MISSED_NO_ANSWER = 65536L; // 0x10000L
+ field public static final long USER_MISSED_NO_VIBRATE = 1048576L; // 0x100000L
+ field public static final long USER_MISSED_SHORT_RING = 131072L; // 0x20000L
field public static final String VIA_NUMBER = "via_number";
field public static final int VOICEMAIL_TYPE = 4; // 0x4
field public static final String VOICEMAIL_URI = "voicemail_uri";
@@ -43850,6 +43864,7 @@
field public static final int TYPE_RANGE = 2; // 0x2
field public static final int TYPE_STATELESS = 8; // 0x8
field public static final int TYPE_TEMPERATURE = 7; // 0x7
+ field public static final int TYPE_THUMBNAIL = 3; // 0x3
field public static final int TYPE_TOGGLE = 1; // 0x1
field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6
}
@@ -43889,6 +43904,14 @@
field public static final int MODE_UNKNOWN = 0; // 0x0
}
+ public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
+ ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
+ method @NonNull public CharSequence getContentDescription();
+ method public int getTemplateType();
+ method @NonNull public android.graphics.drawable.Icon getThumbnail();
+ method public boolean isActive();
+ }
+
public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate);
ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate);
@@ -46486,6 +46509,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
+ method public boolean hasCompanionInCallServiceAccess();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
@@ -47883,6 +47907,7 @@
method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
+ method public int getNrState();
method @Nullable public String getRegisteredPlmn();
method public int getTransportType();
method public boolean isRegistered();
@@ -53754,7 +53779,6 @@
}
public interface OnReceiveContentCallback<T extends android.view.View> {
- method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T);
method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
}
@@ -54332,7 +54356,7 @@
method @IdRes public int getNextFocusRightId();
method @IdRes public int getNextFocusUpId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
- method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback();
+ method @Nullable public String[] getOnReceiveContentMimeTypes();
method @ColorInt public int getOutlineAmbientShadowColor();
method public android.view.ViewOutlineProvider getOutlineProvider();
method @ColorInt public int getOutlineSpotShadowColor();
@@ -54527,6 +54551,7 @@
method public void onProvideContentCaptureStructure(@NonNull android.view.ViewStructure, int);
method public void onProvideStructure(android.view.ViewStructure);
method public void onProvideVirtualStructure(android.view.ViewStructure);
+ method public boolean onReceiveContent(@NonNull android.view.OnReceiveContentCallback.Payload);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method @CallSuper protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
@@ -54684,7 +54709,7 @@
method public void setOnHoverListener(android.view.View.OnHoverListener);
method public void setOnKeyListener(android.view.View.OnKeyListener);
method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener);
- method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>);
+ method public void setOnReceiveContentCallback(@Nullable String[], @Nullable android.view.OnReceiveContentCallback);
method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
method public void setOnTouchListener(android.view.View.OnTouchListener);
@@ -61514,7 +61539,6 @@
method public int getMinWidth();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
- method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback();
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public String getPrivateImeOptions();
@@ -61703,7 +61727,6 @@
public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
ctor public TextViewOnReceiveContentCallback();
- method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView);
method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index de62fd8..c932718 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -403,6 +403,7 @@
field public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats";
field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
+ field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
field public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
@@ -917,7 +918,6 @@
field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2
field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0
field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
- field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
@@ -6695,6 +6695,7 @@
method @NonNull public int[] getTransportTypes();
method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
+ field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
}
@@ -11804,6 +11805,7 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
+ method public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11837,6 +11839,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+ method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
@@ -11876,6 +11879,11 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -11908,6 +11916,9 @@
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
+ field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index 5902006..82838ea 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -84,7 +84,7 @@
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
method public long getTotalRam();
- method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public void holdLock(int);
+ method public void holdLock(android.os.IBinder, int);
method public static boolean isHighEndGfx();
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
@@ -210,6 +210,7 @@
field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
+ field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final int OP_COARSE_LOCATION = 0; // 0x0
field public static final int OP_RECORD_AUDIO = 27; // 0x1b
field public static final int OP_START_FOREGROUND = 76; // 0x4c
@@ -533,6 +534,7 @@
public abstract class PackageManager {
method @Nullable public String getContentCaptureServicePackageName();
method @Nullable public String getDefaultTextClassifierPackageName();
+ method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken();
method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
method @Nullable public abstract String[] getNamesForUids(int[]);
@@ -541,7 +543,7 @@
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
- method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public void holdLock(int);
+ method public void holdLock(android.os.IBinder, int);
field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
@@ -2035,7 +2037,7 @@
}
public interface WindowManager extends android.view.ViewManager {
- method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public default void holdLock(int);
+ method public default void holdLock(android.os.IBinder, int);
method public default void setShouldShowIme(int, boolean);
method public default void setShouldShowSystemDecors(int, boolean);
method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -2374,7 +2376,7 @@
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.app.ActivityManager.RunningTaskInfo createRootTask(int, int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.window.WindowContainerToken getImeTarget(int);
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 0440d1a..c0b4093 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -539,6 +539,8 @@
KotlinOperator: android.os.WorkSource#get(int):
+KotlinOperator: android.util.SparseArrayMap#get(int, K):
+
KotlinOperator: android.util.SparseArrayMap#get(int, String):
@@ -594,17 +596,17 @@
MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setAttributeSet(android.util.AttributeSet):
- android.app.ActivityView does not declare a `getAttributeSet()` method matching method android.app.ActivityView.Builder.setAttributeSet(android.util.AttributeSet)
+
MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setDefaultStyle(int):
- android.app.ActivityView does not declare a `getDefaultStyle()` method matching method android.app.ActivityView.Builder.setDefaultStyle(int)
+
MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setDisableSurfaceViewBackgroundLayer(boolean):
- android.app.ActivityView does not declare a `isDisableSurfaceViewBackgroundLayer()` method matching method android.app.ActivityView.Builder.setDisableSurfaceViewBackgroundLayer(boolean)
+
MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setSingleInstance(boolean):
- android.app.ActivityView does not declare a `isSingleInstance()` method matching method android.app.ActivityView.Builder.setSingleInstance(boolean)
+
MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setUsePublicVirtualDisplay(boolean):
- android.app.ActivityView does not declare a `isUsePublicVirtualDisplay()` method matching method android.app.ActivityView.Builder.setUsePublicVirtualDisplay(boolean)
+
MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setUseTrustedDisplay(boolean):
- android.app.ActivityView does not declare a `isUseTrustedDisplay()` method matching method android.app.ActivityView.Builder.setUseTrustedDisplay(boolean)
+
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setAttributionTag(String):
MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setFlags(int):
@@ -751,6 +753,8 @@
MissingNullability: android.app.ActivityManager#getPackageImportance(String) parameter #0:
+MissingNullability: android.app.ActivityManager#holdLock(android.os.IBinder, int) parameter #0:
+
MissingNullability: android.app.ActivityManager#removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener) parameter #0:
MissingNullability: android.app.ActivityManager#scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int) parameter #0:
@@ -935,8 +939,12 @@
MissingNullability: android.content.pm.PackageInstaller.SessionParams#setGrantedRuntimePermissions(String[]) parameter #0:
+MissingNullability: android.content.pm.PackageManager#getHoldLockToken():
+ Missing nullability on method `BINDER` return
MissingNullability: android.content.pm.PackageManager#getNamesForUids(int[]) parameter #0:
+MissingNullability: android.content.pm.PackageManager#holdLock(android.os.IBinder, int) parameter #0:
+
MissingNullability: android.content.pm.ShortcutManager#ShortcutManager(android.content.Context) parameter #0:
MissingNullability: android.content.res.AssetManager#getOverlayablesToString(String) parameter #0:
@@ -2313,6 +2321,8 @@
MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>) parameter #2:
+MissingNullability: android.view.WindowManager#holdLock(android.os.IBinder, int) parameter #0:
+
MissingNullability: android.view.WindowManager.LayoutParams#accessibilityTitle:
MissingNullability: android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager) parameter #0:
@@ -2893,6 +2903,10 @@
+StartWithLower: android.content.pm.PackageManager#BINDER():
+ Method name must start with lowercase char: BINDER
+
+
StaticFinalBuilder: android.content.integrity.RuleSet.Builder:
StaticFinalBuilder: android.hardware.display.BrightnessConfiguration.Builder:
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c9356c5..1cfe9ee 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -59,6 +59,7 @@
import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto";
import "frameworks/base/core/proto/android/stats/style/style_enums.proto";
import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto";
+import "frameworks/base/core/proto/android/stats/tls/enums.proto";
import "frameworks/base/core/proto/android/telecomm/enums.proto";
import "frameworks/base/core/proto/android/telephony/enums.proto";
import "frameworks/base/core/proto/android/view/enums.proto";
@@ -495,9 +496,11 @@
HdmiCecMessageReported hdmi_cec_message_reported = 310 [(module) = "framework"];
AirplaneMode airplane_mode = 311 [(module) = "telephony"];
ModemRestart modem_restart = 312 [(module) = "telephony"];
- CarrierIdMismatchEvent carrier_id_mismatch_event = 313 [(module) = "telephony"];
- CarrierIdMatchingTable carrier_id_table_update = 314 [(module) = "telephony"];
+ CarrierIdMismatchReported carrier_id_mismatch_reported = 313 [(module) = "telephony"];
+ CarrierIdTableUpdated carrier_id_table_updated = 314 [(module) = "telephony"];
DataStallRecoveryReported data_stall_recovery_reported = 315 [(module) = "telephony"];
+ MediametricsMediaParserReported mediametrics_mediaparser_reported = 316;
+ TlsHandshakeReported tls_handshake_reported = 317 [(module) = "conscrypt"];
// StatsdStats tracks platform atoms with ids upto 500.
// Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -605,7 +608,7 @@
10085 [(module) = "mediaprovider"];
IncomingSms incoming_sms = 10086 [(module) = "telephony"];
OutgoingSms outgoing_sms = 10087 [(module) = "telephony"];
- CarrierIdMatchingTable carrier_id_table_version = 10088 [(module) = "telephony"];
+ CarrierIdTableVersion carrier_id_table_version = 10088 [(module) = "telephony"];
DataCallSession data_call_session = 10089 [(module) = "telephony"];
CellularServiceState cellular_service_state = 10090 [(module) = "telephony"];
CellularDataServiceSwitch cellular_data_service_switch = 10091 [(module) = "telephony"];
@@ -4659,7 +4662,7 @@
UNKNOWN = 0;
CHIP_VIEWED = 1;
CHIP_CLICKED = 2;
- reserved 3; // Used only in beta builds, never shipped
+ reserved 3; // Used only in beta builds, never shipped
DIALOG_DISMISS = 4;
DIALOG_LINE_ITEM = 5;
}
@@ -8197,6 +8200,72 @@
}
/**
+ * Track MediaParser (parsing video/audio streams from containers) usage
+ * Logged from:
+ *
+ * frameworks/av/services/mediametrics/statsd_mediaparser.cpp
+ * frameworks/base/apex/media/framework/jni/android_media_MediaParserJNI.cpp
+ */
+message MediametricsMediaParserReported {
+ optional int64 timestamp_nanos = 1;
+ optional string package_name = 2;
+ optional int64 package_version_code = 3;
+
+ // MediaParser specific data.
+ /**
+ * The name of the parser selected for parsing the media, or an empty string
+ * if no parser was selected.
+ */
+ optional string parser_name = 4;
+ /**
+ * Whether the parser was created by name. 1 represents true, and 0
+ * represents false.
+ */
+ optional int32 created_by_name = 5;
+ /**
+ * The parser names in the sniffing pool separated by "|".
+ */
+ optional string parser_pool = 6;
+ /**
+ * The fully qualified name of the last encountered exception, or an empty
+ * string if no exception was encountered.
+ */
+ optional string last_exception = 7;
+ /**
+ * The size of the parsed media in bytes, or -1 if unknown. Note this value
+ * contains intentional random error to prevent media content
+ * identification.
+ */
+ optional int64 resource_byte_count = 8;
+ /**
+ * The duration of the media in milliseconds, or -1 if unknown. Note this
+ * value contains intentional random error to prevent media content
+ * identification.
+ */
+ optional int64 duration_millis = 9;
+ /**
+ * The MIME types of the tracks separated by "|".
+ */
+ optional string track_mime_types = 10;
+ /**
+ * The tracks' RFC 6381 codec strings separated by "|".
+ */
+ optional string track_codecs = 11;
+ /**
+ * Concatenation of the parameters altered by the client, separated by "|".
+ */
+ optional string altered_parameters = 12;
+ /**
+ * The video width in pixels, or -1 if unknown or not applicable.
+ */
+ optional int32 video_width = 13;
+ /**
+ * The video height in pixels, or -1 if unknown or not applicable.
+ */
+ optional int32 video_height = 14;
+}
+
+/**
* Track how we arbitrate between microphone/input requests.
* Logged from
* frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -10679,7 +10748,7 @@
}
/**
- * Push information about usage of airplane mode.
+ * Logs information about usage of airplane mode.
*
* Logged from:
* frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/AirplaneModeStats.java
@@ -10698,7 +10767,7 @@
}
/**
- * Push information about modem restarts.
+ * Logs information about modem restarts.
*
* Logged from:
* frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/ModemRestartStats.java
@@ -10716,7 +10785,7 @@
}
/**
- * Push the SIM card details when the carrier ID match is not complete.
+ * Logs the SIM card details when the carrier ID match is not complete.
*
* The atom is pushed when a SIM card is initialized and the MCC/MNC is not present in the
* carrier ID table, or the SIM card contains a GID1 value that is not present in the carrier ID
@@ -10725,7 +10794,7 @@
* Logged from:
* frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/CarrierIdMatchStats.java
*/
-message CarrierIdMismatchEvent {
+message CarrierIdMismatchReported {
// Matched carrier ID. The value -1 is used if no match is found.
optional int32 carrier_id = 1;
@@ -10737,17 +10806,30 @@
// SPN value of the SIM card.
optional string spn = 4;
+
+ // First record of the PNN in the SIM card. This field is populated only if the SPN is missing
+ // or empty.
+ optional string pnn = 5;
}
/**
- * Pulls/pushes the version of the carrier ID matching table.
- *
- * The atom is pushed when a new version is detected.
+ * Logs the version of the carrier ID matching table at first power up and when it is updated.
*
* Logged from:
* frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/CarrierIdMatchStats.java
*/
-message CarrierIdMatchingTable {
+message CarrierIdTableUpdated {
+ // Version of the CarrierId matching table.
+ optional int32 table_version = 1;
+}
+
+/**
+ * Pulls the version of the carrier ID matching table.
+ *
+ * Logged from:
+ * frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+ */
+message CarrierIdTableVersion {
// Version of the CarrierId matching table.
optional int32 table_version = 1;
}
@@ -11888,3 +11970,19 @@
// The reason for the feature abort.
optional android.stats.hdmi.FeatureAbortReason feature_abort_reason = 9;
}
+
+/**
+ * Pushes TLS handshake counters from Conscrypt.
+ * Pulled from:
+ * external/conscrypt/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
+ * external/conscrypt/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
+ */
+message TlsHandshakeReported {
+ optional bool success = 1;
+
+ optional android.stats.tls.Protocol protocol = 2;
+
+ optional android.stats.tls.CipherSuite cipher_suite = 3;
+
+ optional int32 handshake_duration_millis = 4;
+}
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 3d57cfe..22fdf16 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -18,12 +18,14 @@
#include "Log.h"
#include "ValueMetricProducer.h"
-#include "../guardrail/StatsdStats.h"
-#include "../stats_log_util.h"
#include <limits.h>
#include <stdlib.h>
+#include "../guardrail/StatsdStats.h"
+#include "../stats_log_util.h"
+#include "metrics/parsing_utils/metrics_manager_util.h"
+
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_DOUBLE;
@@ -184,6 +186,48 @@
}
}
+bool ValueMetricProducer::onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!MetricProducer::onConfigUpdatedLocked(
+ config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+ return false;
+ }
+
+ const ValueMetric& metric = config.value_metric(configIndex);
+ // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps.
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+ trackerToMetricMap, mWhatMatcherIndex)) {
+ return false;
+ }
+
+ if (metric.has_condition() &&
+ !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, mConditionTrackerIndex,
+ conditionToMetricMap)) {
+ return false;
+ }
+ sp<EventMatcherWizard> tmpEventWizard = mEventMatcherWizard;
+ mEventMatcherWizard = matcherWizard;
+ return true;
+}
+
void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId,
const HashableDimensionKey& primaryKey,
const FieldValue& oldState, const FieldValue& newState) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 67de214..ebd8fec 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -47,7 +47,7 @@
// - a condition change
// - an app upgrade
// - an alarm set to the end of the bucket
-class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+class ValueMetricProducer : public MetricProducer, public virtual PullDataReceiver {
public:
ValueMetricProducer(
const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex,
@@ -155,7 +155,23 @@
// causes the bucket to be invalidated will not notify StatsdStats.
void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
- const int mWhatMatcherIndex;
+ bool onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation) override;
+
+ int mWhatMatcherIndex;
sp<EventMatcherWizard> mEventMatcherWizard;
@@ -370,6 +386,8 @@
FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue);
FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateValueMetrics);
+
friend class ValueMetricProducerTestHelper;
};
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index b6e5d88..335f775 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -580,7 +580,6 @@
return false;
}
}
- // TODO: determine update status for value metrics.
return true;
}
@@ -644,7 +643,7 @@
set<int64_t>& noReportMetricIds,
unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
- vector<int>& metricsWithActivation) {
+ vector<int>& metricsWithActivation, set<int64_t>& replacedMetrics) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
@@ -690,6 +689,8 @@
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createCountMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, currentTimeNs, metric, metricIndex,
@@ -727,6 +728,8 @@
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createDurationMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, currentTimeNs, metric, metricIndex,
@@ -749,8 +752,8 @@
newMetricProducers.push_back(producer.value());
}
for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
- newMetricProducerMap[config.event_metric(i).id()] = metricIndex;
const EventMetric& metric = config.event_metric(i);
+ newMetricProducerMap[metric.id()] = metricIndex;
optional<sp<MetricProducer>> producer;
switch (metricsToUpdate[metricIndex]) {
case UPDATE_PRESERVE: {
@@ -764,6 +767,8 @@
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createEventMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers,
@@ -784,6 +789,47 @@
}
newMetricProducers.push_back(producer.value());
}
+
+ for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) {
+ const ValueMetric& metric = config.value_metric(i);
+ newMetricProducerMap[metric.id()] = metricIndex;
+ optional<sp<MetricProducer>> producer;
+ switch (metricsToUpdate[metricIndex]) {
+ case UPDATE_PRESERVE: {
+ producer = updateMetric(
+ config, i, metricIndex, metric.id(), allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
+ case UPDATE_NEW: {
+ producer = createValueMetricProducerAndUpdateMetadata(
+ key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
+ stateAtomIdMap, allStateGroupMaps, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ default: {
+ ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+ (long long)metric.id());
+ return false;
+ }
+ }
+ if (!producer) {
+ return false;
+ }
+ newMetricProducers.push_back(producer.value());
+ }
+
for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) {
const GaugeMetric& metric = config.gauge_metric(i);
newMetricProducerMap[metric.id()] = metricIndex;
@@ -800,6 +846,8 @@
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createGaugeMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex,
@@ -821,7 +869,6 @@
}
newMetricProducers.push_back(producer.value());
}
- // TODO: perform update for value metric.
const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(),
config.whitelisted_atom_ids().end());
@@ -872,6 +919,7 @@
set<int64_t>& noReportMetricIds) {
set<int64_t> replacedMatchers;
set<int64_t> replacedConditions;
+ set<int64_t> replacedMetrics;
vector<ConditionState> conditionCache;
unordered_map<int64_t, int> stateAtomIdMap;
unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
@@ -913,7 +961,7 @@
replacedStates, oldMetricProducerMap, oldMetricProducers,
newMetricProducerMap, newMetricProducers, conditionToMetricMap,
trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap,
- deactivationTrackerToMetricMap, metricsWithActivation)) {
+ deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics)) {
ALOGE("initMetricProducers failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
index 34d7e9c..3f1c532 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -187,7 +187,7 @@
std::set<int64_t>& noReportMetricIds,
std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
- std::vector<int>& metricsWithActivation);
+ std::vector<int>& metricsWithActivation, std::set<int64_t>& replacedMetrics);
// Updates the existing MetricsManager from a new StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index b7dc2c7..8fc039a 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -599,6 +599,108 @@
eventDeactivationMap)};
}
+optional<sp<MetricProducer>> createValueMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const ValueMetric& metric, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!metric.has_id() || !metric.has_what()) {
+ ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+ if (!metric.has_value_field()) {
+ ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+ std::vector<Matcher> fieldMatchers;
+ translateFieldMatcher(metric.value_field(), &fieldMatchers);
+ if (fieldMatchers.size() < 1) {
+ ALOGE("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+
+ int trackerIndex;
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+ metric.has_dimensions_in_what(),
+ allAtomMatchingTrackers, atomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return nullopt;
+ }
+
+ sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
+ // If it is pulled atom, it should be simple matcher with one tagId.
+ if (atomMatcher->getAtomIds().size() != 1) {
+ return nullopt;
+ }
+ int atomTagId = *(atomMatcher->getAtomIds().begin());
+ int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
+ return nullopt;
+ }
+ } else if (metric.links_size() > 0) {
+ ALOGE("metrics has a MetricConditionLink but doesn't have a condition");
+ return nullopt;
+ }
+
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return nullopt;
+ }
+ } else if (metric.state_link_size() > 0) {
+ ALOGE("ValueMetric has a MetricStateLink but doesn't have a sliced state");
+ return nullopt;
+ }
+
+ // Check that all metric state links are a subset of dimensions_in_what fields.
+ std::vector<Matcher> dimensionsInWhat;
+ translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+ for (const auto& stateLink : metric.state_link()) {
+ if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+ return nullopt;
+ }
+ }
+
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap,
+ atomMatchingTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation,
+ eventActivationMap, eventDeactivationMap)) {
+ return nullopt;
+ }
+
+ uint64_t metricHash;
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ return nullopt;
+ }
+
+ return {new ValueMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+ metricHash, trackerIndex, matcherWizard, pullTagId, timeBaseNs,
+ currentTimeNs, pullerManager, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
+}
+
optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
@@ -911,97 +1013,20 @@
// build ValueMetricProducer
for (int i = 0; i < config.value_metric_size(); i++) {
- const ValueMetric& metric = config.value_metric(i);
- if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
- return false;
- }
- if (!metric.has_value_field()) {
- ALOGW("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
- return false;
- }
- std::vector<Matcher> fieldMatchers;
- translateFieldMatcher(metric.value_field(), &fieldMatchers);
- if (fieldMatchers.size() < 1) {
- ALOGW("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
- return false;
- }
-
int metricIndex = allMetricProducers.size();
+ const ValueMetric& metric = config.value_metric(i);
metricMap.insert({metric.id(), metricIndex});
- int trackerIndex;
- if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
- metric.has_dimensions_in_what(),
- allAtomMatchingTrackers, atomMatchingTrackerMap,
- trackerToMetricMap, trackerIndex)) {
- return false;
- }
-
- sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
- // If it is pulled atom, it should be simple matcher with one tagId.
- if (atomMatcher->getAtomIds().size() != 1) {
- return false;
- }
- int atomTagId = *(atomMatcher->getAtomIds().begin());
- int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
-
- int conditionIndex = -1;
- if (metric.has_condition()) {
- bool good = handleMetricWithConditions(
- metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
- allConditionTrackers, conditionIndex, conditionToMetricMap);
- if (!good) {
- return false;
- }
- } else {
- if (metric.links_size() > 0) {
- ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
- return false;
- }
- }
-
- std::vector<int> slicedStateAtoms;
- unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
- if (metric.slice_by_state_size() > 0) {
- if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
- allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
- return false;
- }
- } else {
- if (metric.state_link_size() > 0) {
- ALOGW("ValueMetric has a MetricStateLink but doesn't have a sliced state");
- return false;
- }
- }
-
- // Check that all metric state links are a subset of dimensions_in_what fields.
- std::vector<Matcher> dimensionsInWhat;
- translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
- for (const auto& stateLink : metric.state_link()) {
- if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
- return false;
- }
- }
-
- unordered_map<int, shared_ptr<Activation>> eventActivationMap;
- unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- bool success = handleMetricActivation(
- config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+ optional<sp<MetricProducer>> producer = createValueMetricProducerAndUpdateMetadata(
+ key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex,
+ allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap,
+ allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap);
- if (!success) return false;
-
- uint64_t metricHash;
- if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ metricsWithActivation);
+ if (!producer) {
return false;
}
-
- sp<MetricProducer> valueProducer = new ValueMetricProducer(
- key, metric, conditionIndex, initialConditionCache, wizard, metricHash,
- trackerIndex, matcherWizard, pullTagId, timeBaseTimeNs, currentTimeNs,
- pullerManager, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
- stateGroupMap);
- allMetricProducers.push_back(valueProducer);
+ allMetricProducers.push_back(producer.value());
}
// Gauge metrics.
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index 6d1e6dd..e4585cd 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -148,6 +148,27 @@
std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
std::vector<int>& metricsWithActivation);
+// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with
+// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
+optional<sp<MetricProducer>> createValueMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const ValueMetric& metric, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::unordered_map<int64_t, int>& stateAtomIdMap,
+ const std::unordered_map<int64_t, std::unordered_map<int, int64_t>>& allStateGroupMaps,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
// Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with
// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 0066030..4fa9bf6 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -29,6 +29,7 @@
#include "src/matchers/CombinationAtomMatchingTracker.h"
#include "src/metrics/DurationMetricProducer.h"
#include "src/metrics/GaugeMetricProducer.h"
+#include "src/metrics/ValueMetricProducer.h"
#include "src/metrics/parsing_utils/metrics_manager_util.h"
#include "tests/statsd_test_util.h"
@@ -1811,6 +1812,7 @@
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -1819,13 +1821,14 @@
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{event1Id, event1Index}, {event2Id, event2Index}, {event3Id, event3Index},
{event4Id, event4Index}, {event6Id, event6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({event2Id, event3Id, event4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
@@ -2040,6 +2043,7 @@
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2048,13 +2052,14 @@
oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{count1Id, count1Index}, {count2Id, count2Index}, {count3Id, count3Index},
{count4Id, count4Index}, {count6Id, count6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({count2Id, count3Id, count4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
@@ -2249,6 +2254,7 @@
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2257,13 +2263,14 @@
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{gauge1Id, gauge1Index}, {gauge2Id, gauge2Index}, {gauge3Id, gauge3Index},
{gauge4Id, gauge4Index}, {gauge6Id, gauge6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({gauge2Id, gauge3Id, gauge4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
@@ -2566,6 +2573,7 @@
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
@@ -2574,7 +2582,7 @@
oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{duration1Id, duration1Index}, {duration2Id, duration2Index},
@@ -2582,7 +2590,7 @@
{duration6Id, duration6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
-
+ EXPECT_EQ(replacedMetrics, set<int64_t>({duration2Id, duration3Id, duration4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(duration1Id)],
@@ -2685,6 +2693,245 @@
EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
}
+TEST_F(ConfigUpdateTest, TestUpdateValueMetrics) {
+ StatsdConfig config;
+
+ // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig.
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ AtomMatcher matcher4 = CreateTemperatureAtomMatcher();
+ int64_t matcher4Id = matcher4.id();
+ *config.add_atom_matcher() = matcher4;
+
+ AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+ int64_t matcher5Id = matcher5.id();
+ *config.add_atom_matcher() = matcher5;
+
+ Predicate predicate1 = CreateScreenIsOnPredicate();
+ int64_t predicate1Id = predicate1.id();
+ *config.add_predicate() = predicate1;
+
+ Predicate predicate2 = CreateScreenIsOffPredicate();
+ int64_t predicate2Id = predicate2.id();
+ *config.add_predicate() = predicate2;
+
+ State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321);
+ int64_t state1Id = state1.id();
+ *config.add_state() = state1;
+
+ State state2 = CreateScreenState();
+ int64_t state2Id = state2.id();
+ *config.add_state() = state2;
+
+ // Add a few value metrics.
+ // Note that these will not work as "real" metrics since the value field is always 2.
+ // Will be preserved.
+ ValueMetric value1 = createValueMetric("VALUE1", matcher4, predicate1Id, {state1Id});
+ int64_t value1Id = value1.id();
+ *config.add_value_metric() = value1;
+
+ // Will be replaced - definition change.
+ ValueMetric value2 = createValueMetric("VALUE2", matcher1, nullopt, {});
+ int64_t value2Id = value2.id();
+ *config.add_value_metric() = value2;
+
+ // Will be replaced - condition change.
+ ValueMetric value3 = createValueMetric("VALUE3", matcher5, predicate2Id, {});
+ int64_t value3Id = value3.id();
+ *config.add_value_metric() = value3;
+
+ // Will be replaced - state change.
+ ValueMetric value4 = createValueMetric("VALUE4", matcher3, nullopt, {state2Id});
+ int64_t value4Id = value4.id();
+ *config.add_value_metric() = value4;
+
+ // Will be deleted.
+ ValueMetric value5 = createValueMetric("VALUE5", matcher2, nullopt, {});
+ int64_t value5Id = value5.id();
+ *config.add_value_metric() = value5;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Used later to ensure the condition wizard is replaced. Get it before doing the update.
+ sp<EventMatcherWizard> oldMatcherWizard =
+ static_cast<ValueMetricProducer*>(oldMetricProducers[0].get())->mEventMatcherWizard;
+ EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6);
+
+ // Change value2, causing it to be replaced.
+ value2.set_aggregation_type(ValueMetric::AVG);
+
+ // Mark predicate 2 as replaced. Causes value3 to be replaced.
+ set<int64_t> replacedConditions = {predicate2Id};
+
+ // Mark state 2 as replaced. Causes value4 to be replaced.
+ set<int64_t> replacedStates = {state2Id};
+
+ // New value metric.
+ ValueMetric value6 = createValueMetric("VALUE6", matcher5, predicate1Id, {state1Id});
+ int64_t value6Id = value6.id();
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher5Index = 0;
+ newAtomMatchingTrackerMap[matcher5Id] = 0;
+ const int matcher4Index = 1;
+ newAtomMatchingTrackerMap[matcher4Id] = 1;
+ const int matcher3Index = 2;
+ newAtomMatchingTrackerMap[matcher3Id] = 2;
+ const int matcher2Index = 3;
+ newAtomMatchingTrackerMap[matcher2Id] = 3;
+ const int matcher1Index = 4;
+ newAtomMatchingTrackerMap[matcher1Id] = 4;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(5);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+
+ std::unordered_map<int64_t, int> newConditionTrackerMap;
+ const int predicate2Index = 0;
+ newConditionTrackerMap[predicate2Id] = 0;
+ const int predicate1Index = 1;
+ newConditionTrackerMap[predicate1Id] = 1;
+ // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<ConditionTracker>> newConditionTrackers(2);
+ std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+ newConditionTrackers.begin());
+ // Say that predicate1 & predicate2 is unknown since the initial condition never changed.
+ vector<ConditionState> conditionCache = {ConditionState::kUnknown, ConditionState::kUnknown};
+
+ StatsdConfig newConfig;
+ *newConfig.add_value_metric() = value6;
+ const int value6Index = 0;
+ *newConfig.add_value_metric() = value3;
+ const int value3Index = 1;
+ *newConfig.add_value_metric() = value1;
+ const int value1Index = 2;
+ *newConfig.add_value_metric() = value4;
+ const int value4Index = 3;
+ *newConfig.add_value_metric() = value2;
+ const int value2Index = 4;
+
+ *newConfig.add_state() = state1;
+ *newConfig.add_state() = state2;
+
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
+ map<int64_t, uint64_t> stateProtoHashes;
+ EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes));
+
+ // Output data structures to validate.
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
+ EXPECT_TRUE(updateMetrics(
+ key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
+ newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
+ newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
+ oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
+ conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation, replacedMetrics));
+
+ unordered_map<int64_t, int> expectedMetricProducerMap = {
+ {value1Id, value1Index}, {value2Id, value2Index}, {value3Id, value3Index},
+ {value4Id, value4Index}, {value6Id, value6Index},
+ };
+ EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({value2Id, value3Id, value4Id}));
+
+ // Make sure preserved metrics are the same.
+ ASSERT_EQ(newMetricProducers.size(), 5);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(value1Id)],
+ newMetricProducers[newMetricProducerMap.at(value1Id)]);
+
+ // Make sure replaced metrics are different.
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value2Id)],
+ newMetricProducers[newMetricProducerMap.at(value2Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value3Id)],
+ newMetricProducers[newMetricProducerMap.at(value3Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value4Id)],
+ newMetricProducers[newMetricProducerMap.at(value4Id)]);
+
+ // Verify the conditionToMetricMap.
+ ASSERT_EQ(conditionToMetricMap.size(), 2);
+ const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
+ EXPECT_THAT(condition1Metrics, UnorderedElementsAre(value1Index, value6Index));
+ const vector<int>& condition2Metrics = conditionToMetricMap[predicate2Index];
+ EXPECT_THAT(condition2Metrics, UnorderedElementsAre(value3Index));
+
+ // Verify the trackerToMetricMap.
+ ASSERT_EQ(trackerToMetricMap.size(), 4);
+ const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+ EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(value2Index));
+ const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(value4Index));
+ const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index];
+ EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(value1Index));
+ const vector<int>& matcher5Metrics = trackerToMetricMap[matcher5Index];
+ EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(value3Index, value6Index));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(metricsWithActivation.size(), 0);
+
+ // Verify tracker indices/ids/conditions/states are correct.
+ ValueMetricProducer* valueProducer1 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value1Index].get());
+ EXPECT_EQ(valueProducer1->getMetricId(), value1Id);
+ EXPECT_EQ(valueProducer1->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(valueProducer1->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(valueProducer1->mWhatMatcherIndex, matcher4Index);
+ ValueMetricProducer* valueProducer2 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value2Index].get());
+ EXPECT_EQ(valueProducer2->getMetricId(), value2Id);
+ EXPECT_EQ(valueProducer2->mConditionTrackerIndex, -1);
+ EXPECT_EQ(valueProducer2->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(valueProducer2->mWhatMatcherIndex, matcher1Index);
+ ValueMetricProducer* valueProducer3 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value3Index].get());
+ EXPECT_EQ(valueProducer3->getMetricId(), value3Id);
+ EXPECT_EQ(valueProducer3->mConditionTrackerIndex, predicate2Index);
+ EXPECT_EQ(valueProducer3->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(valueProducer3->mWhatMatcherIndex, matcher5Index);
+ ValueMetricProducer* valueProducer4 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value4Index].get());
+ EXPECT_EQ(valueProducer4->getMetricId(), value4Id);
+ EXPECT_EQ(valueProducer4->mConditionTrackerIndex, -1);
+ EXPECT_EQ(valueProducer4->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(valueProducer4->mWhatMatcherIndex, matcher3Index);
+ ValueMetricProducer* valueProducer6 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value6Index].get());
+ EXPECT_EQ(valueProducer6->getMetricId(), value6Id);
+ EXPECT_EQ(valueProducer6->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(valueProducer6->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(valueProducer6->mWhatMatcherIndex, matcher5Index);
+
+ sp<EventMatcherWizard> newMatcherWizard = valueProducer1->mEventMatcherWizard;
+ EXPECT_NE(newMatcherWizard, oldMatcherWizard);
+ EXPECT_EQ(newMatcherWizard->getStrongCount(), 6);
+ oldMetricProducers.clear();
+ // Only reference to the old wizard should be the one in the test.
+ EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1);
+}
+
TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) {
StatsdConfig config;
// Add atom matchers
@@ -2767,6 +3014,7 @@
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2775,7 +3023,7 @@
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
// Verify event activation/deactivation maps.
ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 3);
@@ -2850,6 +3098,11 @@
*config.add_gauge_metric() = gaugeMetric;
// Preserved.
+ ValueMetric valueMetric = createValueMetric("VALUE1", matcher3, predicate1Id, {});
+ int64_t valueMetricId = valueMetric.id();
+ *config.add_value_metric() = valueMetric;
+
+ // Preserved.
DurationMetric durationMetric = createDurationMetric("DURATION1", predicate1Id, nullopt, {});
int64_t durationMetricId = durationMetric.id();
*config.add_duration_metric() = durationMetric;
@@ -2858,7 +3111,7 @@
// Used later to ensure the condition wizard is replaced. Get it before doing the update.
sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard;
- EXPECT_EQ(oldConditionWizard->getStrongCount(), 5);
+ EXPECT_EQ(oldConditionWizard->getStrongCount(), 6);
// Mark matcher 2 as replaced. Causes eventMetric to be replaced.
set<int64_t> replacedMatchers;
@@ -2889,6 +3142,7 @@
newConditionTrackers.begin());
vector<ConditionState> conditionCache = {ConditionState::kUnknown};
+ // The order matters. we parse in the order of: count, duration, event, value, gauge.
StatsdConfig newConfig;
*newConfig.add_count_metric() = countMetric;
const int countMetricIndex = 0;
@@ -2896,8 +3150,10 @@
const int durationMetricIndex = 1;
*newConfig.add_event_metric() = eventMetric;
const int eventMetricIndex = 2;
+ *newConfig.add_value_metric() = valueMetric;
+ const int valueMetricIndex = 3;
*newConfig.add_gauge_metric() = gaugeMetric;
- const int gaugeMetricIndex = 3;
+ const int gaugeMetricIndex = 4;
// Add the predicate since duration metric needs it.
*newConfig.add_predicate() = predicate1;
@@ -2911,6 +3167,7 @@
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2919,22 +3176,25 @@
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
- {countMetricId, countMetricIndex},
- {durationMetricId, durationMetricIndex},
- {eventMetricId, eventMetricIndex},
+ {countMetricId, countMetricIndex}, {durationMetricId, durationMetricIndex},
+ {eventMetricId, eventMetricIndex}, {valueMetricId, valueMetricIndex},
{gaugeMetricId, gaugeMetricIndex},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({eventMetricId, gaugeMetricId}));
+
// Make sure preserved metrics are the same.
- ASSERT_EQ(newMetricProducers.size(), 4);
+ ASSERT_EQ(newMetricProducers.size(), 5);
EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)],
newMetricProducers[newMetricProducerMap.at(countMetricId)]);
EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(durationMetricId)],
newMetricProducers[newMetricProducerMap.at(durationMetricId)]);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(valueMetricId)],
+ newMetricProducers[newMetricProducerMap.at(valueMetricId)]);
// Make sure replaced metrics are different.
EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)],
@@ -2945,7 +3205,8 @@
// Verify the conditionToMetricMap.
ASSERT_EQ(conditionToMetricMap.size(), 1);
const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
- EXPECT_THAT(condition1Metrics, UnorderedElementsAre(countMetricIndex, gaugeMetricIndex));
+ EXPECT_THAT(condition1Metrics,
+ UnorderedElementsAre(countMetricIndex, gaugeMetricIndex, valueMetricIndex));
// Verify the trackerToMetricMap.
ASSERT_EQ(trackerToMetricMap.size(), 3);
@@ -2954,7 +3215,7 @@
const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex, durationMetricIndex));
const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
- EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex));
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex, valueMetricIndex));
// Verify event activation/deactivation maps.
ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
@@ -2977,7 +3238,7 @@
sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard;
EXPECT_NE(newConditionWizard, oldConditionWizard);
- EXPECT_EQ(newConditionWizard->getStrongCount(), 5);
+ EXPECT_EQ(newConditionWizard->getStrongCount(), 6);
oldMetricProducers.clear();
// Only reference to the old wizard should be the one in the test.
EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 250f2f0..74da95f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4812,13 +4812,14 @@
/**
* Holds the AM lock for the specified amount of milliseconds.
* This is intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
* @hide
*/
@TestApi
- @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
- public void holdLock(int durationMs) {
+ public void holdLock(IBinder token, int durationMs) {
try {
- getService().holdLock(durationMs);
+ getService().holdLock(token, durationMs);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f3b3789..3bcb87a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -865,6 +865,8 @@
@UnsupportedAppUsage
public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS;
/** @hide */
+ public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS;
+ /** @hide */
@UnsupportedAppUsage
public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS;
/** @hide */
@@ -1155,7 +1157,7 @@
/** @hide */
@UnsupportedAppUsage
- public static final int _NUM_OP = 103;
+ public static final int _NUM_OP = 104;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1466,6 +1468,15 @@
public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats";
/**
+ * Grants an app access to the {@link android.telecom.InCallService} API to see
+ * information about ongoing calls and to enable control of calls.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
+
+ /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -1574,6 +1585,7 @@
OP_MANAGE_EXTERNAL_STORAGE,
OP_INTERACT_ACROSS_PROFILES,
OP_LOADER_USAGE_STATS,
+ OP_MANAGE_ONGOING_CALLS,
};
/**
@@ -1688,6 +1700,7 @@
OP_PHONE_CALL_MICROPHONE, // OP_PHONE_CALL_MICROPHONE
OP_PHONE_CALL_CAMERA, // OP_PHONE_CALL_CAMERA
OP_RECORD_AUDIO_HOTWORD, // RECORD_AUDIO_HOTWORD
+ OP_MANAGE_ONGOING_CALLS, // MANAGE_ONGOING_CALLS
};
/**
@@ -1797,6 +1810,7 @@
OPSTR_PHONE_CALL_MICROPHONE,
OPSTR_PHONE_CALL_CAMERA,
OPSTR_RECORD_AUDIO_HOTWORD,
+ OPSTR_MANAGE_ONGOING_CALLS,
};
/**
@@ -1907,6 +1921,7 @@
"PHONE_CALL_MICROPHONE",
"PHONE_CALL_CAMERA",
"RECORD_AUDIO_HOTWORD",
+ "MANAGE_ONGOING_CALLS",
};
/**
@@ -2018,6 +2033,7 @@
null, // no permission for OP_PHONE_CALL_MICROPHONE
null, // no permission for OP_PHONE_CALL_CAMERA
null, // no permission for OP_RECORD_AUDIO_HOTWORD
+ Manifest.permission.MANAGE_ONGOING_CALLS,
};
/**
@@ -2129,6 +2145,7 @@
null, // PHONE_CALL_MICROPHONE
null, // PHONE_CALL_MICROPHONE
null, // RECORD_AUDIO_HOTWORD
+ null, // MANAGE_ONGOING_CALLS
};
/**
@@ -2239,6 +2256,7 @@
null, // PHONE_CALL_MICROPHONE
null, // PHONE_CALL_CAMERA
null, // RECORD_AUDIO_HOTWORD
+ null, // MANAGE_ONGOING_CALLS
};
/**
@@ -2348,6 +2366,7 @@
AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD
+ AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS
};
/**
@@ -2461,6 +2480,7 @@
false, // PHONE_CALL_MICROPHONE
false, // PHONE_CALL_CAMERA
false, // RECORD_AUDIO_HOTWORD
+ true, // MANAGE_ONGOING_CALLS
};
/**
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 357b26c..c0e3019 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -689,6 +689,8 @@
/**
* Holds the AM lock for the specified amount of milliseconds.
* This is intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
*/
- void holdLock(in int durationMs);
+ void holdLock(in IBinder token, in int durationMs);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 861e437..37c4c92 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -130,6 +130,7 @@
FLAG_UPDATE_CURRENT,
FLAG_IMMUTABLE,
FLAG_MUTABLE,
+ FLAG_MUTABLE_UNAUDITED,
Intent.FILL_IN_ACTION,
Intent.FILL_IN_DATA,
@@ -205,6 +206,13 @@
public static final int FLAG_MUTABLE = 1<<25;
/**
+ * @deprecated Use {@link #FLAG_IMMUTABLE} or {@link #FLAG_MUTABLE} instead.
+ * @hide
+ */
+ @Deprecated
+ public static final int FLAG_MUTABLE_UNAUDITED = FLAG_MUTABLE;
+
+ /**
* Exception thrown when trying to send through a PendingIntent that
* has been canceled or is otherwise no longer able to execute the request.
*/
@@ -397,6 +405,7 @@
* parameters. May return null only if {@link #FLAG_NO_CREATE} has been
* supplied.
*/
+ @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
public static PendingIntent getActivity(Context context, int requestCode,
@NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
// Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null
@@ -528,6 +537,7 @@
* parameters. May return null only if {@link #FLAG_NO_CREATE} has been
* supplied.
*/
+ @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
public static PendingIntent getActivities(Context context, int requestCode,
@NonNull Intent[] intents, @Flags int flags, @Nullable Bundle options) {
// Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 849f679..5caf305 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -24,6 +24,8 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
@@ -181,6 +183,20 @@
public boolean isResizeable;
/**
+ * Activity bounds if this task or its top activity is presented in letterbox mode and
+ * {@code null} otherwise.
+ * @hide
+ */
+ @Nullable
+ public Rect letterboxActivityBounds;
+
+ /**
+ * Relative position of the task's top left corner in the parent container.
+ * @hide
+ */
+ public Point positionInParent;
+
+ /**
* The launch cookies associated with activities in this task if any.
* @see ActivityOptions#setLaunchCookie(IBinder)
* @hide
@@ -225,6 +241,7 @@
/** @hide */
public void addLaunchCookie(IBinder cookie) {
+ if (cookie == null || launchCookies.contains(cookie)) return;
launchCookies.add(cookie);
}
@@ -256,6 +273,8 @@
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
isResizeable = source.readBoolean();
source.readBinderList(launchCookies);
+ letterboxActivityBounds = source.readTypedObject(Rect.CREATOR);
+ positionInParent = source.readTypedObject(Point.CREATOR);
}
/**
@@ -287,6 +306,8 @@
dest.writeTypedObject(topActivityInfo, flags);
dest.writeBoolean(isResizeable);
dest.writeBinderList(launchCookies);
+ dest.writeTypedObject(letterboxActivityBounds, flags);
+ dest.writeTypedObject(positionInParent, flags);
}
@Override
@@ -306,6 +327,9 @@
+ " topActivityType=" + topActivityType
+ " pictureInPictureParams=" + pictureInPictureParams
+ " topActivityInfo=" + topActivityInfo
- + " launchCookies" + launchCookies;
+ + " launchCookies" + launchCookies
+ + " letterboxActivityBounds=" + letterboxActivityBounds
+ + " positionInParent=" + positionInParent
+ + "}";
}
}
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 4ae1670..c4af4ed 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -796,6 +796,9 @@
/**
* Returns {@code true} if the windowingMode represents a window in multi-window mode.
* I.e. sharing the screen with another activity.
+ *
+ * TODO(b/171672645): This API could be misleading - in 'undefined' mode it's determined by the
+ * parent's mode
* @hide
*/
public static boolean inMultiWindowMode(int windowingMode) {
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 3cc7f1e..4c541b3 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -55,22 +55,6 @@
static final String TAG = "DeviceAdminInfo";
/**
- * A type of policy that this device admin can use: device owner meta-policy
- * for an admin that is designated as owner of the device.
- *
- * @hide
- */
- public static final int USES_POLICY_DEVICE_OWNER = -2;
-
- /**
- * A type of policy that this device admin can use: profile owner meta-policy
- * for admins that have been installed as owner of some user profile.
- *
- * @hide
- */
- public static final int USES_POLICY_PROFILE_OWNER = -1;
-
- /**
* A type of policy that this device admin can use: limit the passwords
* that the user can select, via {@link DevicePolicyManager#setPasswordQuality}
* and {@link DevicePolicyManager#setPasswordMinimumLength}.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 054e842..8d4de26 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -525,7 +525,7 @@
* @hide
*/
public static final String ACTION_REMOTE_BUGREPORT_DISPATCH =
- "android.intent.action.REMOTE_BUGREPORT_DISPATCH";
+ "com.android.server.action.REMOTE_BUGREPORT_DISPATCH";
/**
* Extra for shared bugreport's SHA-256 hash.
@@ -1830,15 +1830,6 @@
public static final int STATE_USER_PROFILE_COMPLETE = 4;
/**
- * Management setup on a managed profile.
- * <p>This is used as an intermediate state after {@link #STATE_USER_PROFILE_COMPLETE} once the
- * work profile has been created.
- * @hide
- */
- @SystemApi
- public static final int STATE_USER_PROFILE_FINALIZED = 5;
-
- /**
* @hide
*/
@IntDef(prefix = { "STATE_USER_" }, value = {
@@ -1846,8 +1837,7 @@
STATE_USER_SETUP_INCOMPLETE,
STATE_USER_SETUP_COMPLETE,
STATE_USER_SETUP_FINALIZED,
- STATE_USER_PROFILE_COMPLETE,
- STATE_USER_PROFILE_FINALIZED
+ STATE_USER_PROFILE_COMPLETE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserProvisioningState {}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index dedfce6..f9e63085 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -64,12 +65,29 @@
public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
/**
- * The MIME type for an activity.
+ * The MIME type for an activity. The ClipData must include intents with required extras
+ * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
+ * {@link #EXTRA_ACTIVITY_OPTIONS}.
* @hide
*/
public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity";
/**
+ * The MIME type for a shortcut. The ClipData must include intents with required extras
+ * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
+ * {@link #EXTRA_ACTIVITY_OPTIONS}.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut";
+
+ /**
+ * The MIME type for a task. The ClipData must include an intent with a required extra
+ * {@link Intent#EXTRA_TASK_ID} of the task to launch.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_TASK = "application/vnd.android.task";
+
+ /**
* The MIME type for data whose type is otherwise unknown.
* <p>
* Per RFC 2046, the "application" media type is to be used for discrete
@@ -200,6 +218,24 @@
}
/**
+ * Check whether the clip description contains any of the given MIME types.
+ *
+ * @param targetMimeTypes The target MIME types. May use patterns.
+ * @return Returns true if at least one of the MIME types in the clip description matches at
+ * least one of the target MIME types, else false.
+ *
+ * @hide
+ */
+ public boolean hasMimeType(@NonNull String[] targetMimeTypes) {
+ for (String targetMimeType : targetMimeTypes) {
+ if (hasMimeType(targetMimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Filter the clip description MIME types by the given MIME type. Returns
* all MIME types in the clip that match the given MIME type.
*
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index d9ecf46..d688614 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
import android.app.IApplicationThread;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
@@ -55,6 +56,8 @@
void startActivityAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
+ PendingIntent getActivityLaunchIntent(in ComponentName component, in Bundle opts,
+ in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c32d344..106021e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -794,5 +794,7 @@
void grantImplicitAccess(int queryingUid, String visibleAuthority);
- void holdLock(in int durationMs);
+ IBinder getHoldLockToken();
+
+ void holdLock(in IBinder token, in int durationMs);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index b7af397..2909d66 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -17,6 +17,7 @@
package android.content.pm;
import static android.Manifest.permission;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -716,6 +717,29 @@
}
/**
+ * Returns a PendingIntent that would start the same activity started from
+ * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param startActivityOptions Options to pass to startActivity
+ * @param user The UserHandle of the profile
+ * @hide
+ */
+ @Nullable
+ public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
+ @Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ if (DEBUG) {
+ Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
+ }
+ try {
+ return mService.getActivityLaunchIntent(component, startActivityOptions, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3fb9a9e..602bd6c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -63,6 +63,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -8462,15 +8463,30 @@
}
/**
+ * Returns the token to be used by the subsequent calls to holdLock().
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
+ @TestApi
+ public IBinder getHoldLockToken() {
+ try {
+ return ActivityThread.getPackageManager().getHoldLockToken();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Holds the PM lock for the specified amount of milliseconds.
* Intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
* @hide
*/
@TestApi
- @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
- public void holdLock(int durationMs) {
+ public void holdLock(IBinder token, int durationMs) {
try {
- ActivityThread.getPackageManager().holdLock(durationMs);
+ ActivityThread.getPackageManager().holdLock(token, durationMs);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 802655b..2b68989 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -46,7 +46,7 @@
mContext = context;
mTestSession = testSession;
mTestedUsers = new ArraySet<>();
- enableTestHal(true);
+ setTestHalEnabled(true);
}
/**
@@ -56,12 +56,12 @@
* secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
* equivalent for the secret key.
*
- * @param enableTestHal If true, enable testing with a fake HAL instead of the real HAL.
+ * @param enabled If true, enable testing with a fake HAL instead of the real HAL.
*/
@RequiresPermission(TEST_BIOMETRIC)
- private void enableTestHal(boolean enableTestHal) {
+ private void setTestHalEnabled(boolean enabled) {
try {
- mTestSession.enableTestHal(enableTestHal);
+ mTestSession.setTestHalEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -178,10 +178,12 @@
@Override
@RequiresPermission(TEST_BIOMETRIC)
public void close() {
+ // Disable the test HAL first, so that enumerate is run on the real HAL, which should have
+ // no enrollments. Test-only framework enrollments will be deleted.
+ setTestHalEnabled(false);
+
for (int user : mTestedUsers) {
cleanupInternalState(user);
}
-
- enableTestHal(false);
}
}
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
index 6112f17..fa7a62c 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -27,7 +27,7 @@
// portion of the framework code that would otherwise require human interaction. Note that
// secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
// equivalent for the secret key.
- void enableTestHal(boolean enableTestHal);
+ void setTestHalEnabled(boolean enableTestHal);
// Starts the enrollment process. This should generally be used when the test HAL is enabled.
void startEnroll(int userId);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 8cfa086..228617c 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -190,6 +190,7 @@
}
}
+ private final String mCameraId;
@UnsupportedAppUsage
private final CameraMetadataNative mResults;
private final CaptureRequest mRequest;
@@ -202,7 +203,7 @@
* <p>For internal use only</p>
* @hide
*/
- public CaptureResult(CameraMetadataNative results, CaptureRequest parent,
+ public CaptureResult(String cameraId, CameraMetadataNative results, CaptureRequest parent,
CaptureResultExtras extras) {
if (results == null) {
throw new IllegalArgumentException("results was null");
@@ -221,6 +222,7 @@
throw new AssertionError("Results must not be empty");
}
setNativeInstance(mResults);
+ mCameraId = cameraId;
mRequest = parent;
mSequenceId = extras.getRequestId();
mFrameNumber = extras.getFrameNumber();
@@ -251,12 +253,27 @@
}
setNativeInstance(mResults);
+ mCameraId = "none";
mRequest = null;
mSequenceId = sequenceId;
mFrameNumber = -1;
}
/**
+ * Get the camera ID of the camera that produced this capture result.
+ *
+ * For a logical multi-camera, the ID may be the logical or the physical camera ID, depending on
+ * whether the capture result was obtained from
+ * {@link TotalCaptureResult#getPhysicalCameraResults} or not.
+ *
+ * @return The camera ID for the camera that produced this capture result.
+ */
+ @NonNull
+ public String getCameraId() {
+ return mCameraId;
+ }
+
+ /**
* Get a capture result field value.
*
* <p>The field definitions can be found in {@link CaptureResult}.</p>
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 7cc2623..da65f71 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -70,10 +70,10 @@
* @param partials a list of partial results; {@code null} will be substituted for an empty list
* @hide
*/
- public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
- CaptureResultExtras extras, List<CaptureResult> partials, int sessionId,
- PhysicalCaptureResultInfo physicalResults[]) {
- super(results, parent, extras);
+ public TotalCaptureResult(String logicalCameraId, CameraMetadataNative results,
+ CaptureRequest parent, CaptureResultExtras extras, List<CaptureResult> partials,
+ int sessionId, PhysicalCaptureResultInfo[] physicalResults) {
+ super(logicalCameraId, results, parent, extras);
if (partials == null) {
mPartialResults = new ArrayList<>();
@@ -85,7 +85,7 @@
mPhysicalCaptureResults = new HashMap<String, CaptureResult>();
for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
- CaptureResult physicalResult = new CaptureResult(
+ CaptureResult physicalResult = new CaptureResult(onePhysicalResult.getCameraId(),
onePhysicalResult.getCameraMetadata(), parent, extras);
mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
physicalResult);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 48ec3fd..819d966 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1980,7 +1980,7 @@
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
- new CaptureResult(result, request, resultExtras);
+ new CaptureResult(getId(), result, request, resultExtras);
// Partial result
resultDispatch = new Runnable() {
@Override
@@ -1992,7 +1992,7 @@
for (int i = 0; i < holder.getRequestCount(); i++) {
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
- CaptureResult resultInBatch = new CaptureResult(
+ CaptureResult resultInBatch = new CaptureResult(getId(),
resultLocal, holder.getRequest(i), resultExtras);
holder.getCallback().onCaptureProgressed(
@@ -2019,8 +2019,8 @@
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
- final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
- request, resultExtras, partialResults, holder.getSessionId(),
+ final TotalCaptureResult resultAsCapture = new TotalCaptureResult(getId(),
+ result, request, resultExtras, partialResults, holder.getSessionId(),
physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@@ -2038,9 +2038,9 @@
new CameraMetadataNative(resultCopy);
// No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
- resultLocal, holder.getRequest(i), resultExtras,
- partialResults, holder.getSessionId(),
- new PhysicalCaptureResultInfo[0]);
+ getId(), resultLocal, holder.getRequest(i),
+ resultExtras, partialResults, holder.getSessionId(),
+ new PhysicalCaptureResultInfo[0]);
holder.getCallback().onCaptureCompleted(
CameraDeviceImpl.this,
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 1d8b2a1..eb2ff88 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -334,7 +334,7 @@
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
- new CaptureResult(result, request, resultExtras);
+ new CaptureResult(mCameraId, result, request, resultExtras);
// Partial result
resultDispatch = new Runnable() {
@Override
@@ -349,7 +349,8 @@
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
final CaptureResult resultInBatch = new CaptureResult(
- resultLocal, holder.getRequest(i), resultExtras);
+ mCameraId, resultLocal, holder.getRequest(i),
+ resultExtras);
final CaptureRequest cbRequest = holder.getRequest(i);
callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
@@ -372,8 +373,8 @@
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
- final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
- request, resultExtras, partialResults, holder.getSessionId(),
+ final TotalCaptureResult resultAsCapture = new TotalCaptureResult(mCameraId,
+ result, request, resultExtras, partialResults, holder.getSessionId(),
physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@@ -393,9 +394,9 @@
new CameraMetadataNative(resultCopy);
// No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
- resultLocal, holder.getRequest(i), resultExtras,
- partialResults, holder.getSessionId(),
- new PhysicalCaptureResultInfo[0]);
+ mCameraId, resultLocal, holder.getRequest(i),
+ resultExtras, partialResults, holder.getSessionId(),
+ new PhysicalCaptureResultInfo[0]);
final CaptureRequest cbRequest = holder.getRequest(i);
callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index be33f4e..12ddc62 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -170,6 +170,7 @@
NET_CAPABILITY_MCX,
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NET_CAPABILITY_OEM_PRIVATE,
})
public @interface NetCapability { }
@@ -345,8 +346,15 @@
*/
public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
+ /**
+ * Indicates that this network is private to the OEM and meant only for OEM use.
+ * @hide
+ */
+ @SystemApi
+ public static final int NET_CAPABILITY_OEM_PRIVATE = 26;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_OEM_PRIVATE;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -404,7 +412,8 @@
* {@see #maybeMarkCapabilitiesRestricted}.
*/
private static final long FORCE_RESTRICTED_CAPABILITIES =
- (1 << NET_CAPABILITY_OEM_PAID);
+ (1 << NET_CAPABILITY_OEM_PAID)
+ | (1 << NET_CAPABILITY_OEM_PRIVATE);
/**
* Capabilities that suggest that a network is unrestricted.
@@ -1910,6 +1919,7 @@
case NET_CAPABILITY_MCX: return "MCX";
case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED";
+ case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE";
default: return Integer.toString(capability);
}
}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index c39fd4d..8c98362 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -44,7 +44,7 @@
* public void run() {
* Looper.prepare();
*
- * mHandler = new Handler() {
+ * mHandler = new Handler(Looper.myLooper()) {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 8cdcd49..13b30f4 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -908,7 +908,11 @@
Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET);
intent.setPackage(packageName);
PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ UserHandle.SYSTEM);
IntentFilter filterConsent = new IntentFilter();
filterConsent.addAction(ACTION_EUICC_FACTORY_RESET);
HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread");
@@ -1002,7 +1006,11 @@
Intent intent = new Intent(ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS);
intent.setPackage(PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK);
PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ UserHandle.SYSTEM);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS);
HandlerThread euiccHandlerThread =
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c3b6d8e..105ffaa 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -17,6 +17,7 @@
package android.provider;
+import android.annotation.LongDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -43,6 +44,8 @@
import android.text.TextUtils;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -611,6 +614,144 @@
*/
public static final String BLOCK_REASON = "block_reason";
+ /** @hide */
+ @LongDef(flag = true, value = {
+ MISSED_REASON_NOT_MISSED,
+ AUTO_MISSED_EMERGENCY_CALL,
+ AUTO_MISSED_MAXIMUM_RINGING,
+ AUTO_MISSED_MAXIMUM_DIALING,
+ USER_MISSED_NO_ANSWER,
+ USER_MISSED_SHORT_RING,
+ USER_MISSED_DND_MODE,
+ USER_MISSED_LOW_RING_VOLUME,
+ USER_MISSED_NO_VIBRATE,
+ USER_MISSED_CALL_SCREENING_SERVICE_SILENCED,
+ USER_MISSED_CALL_FILTERS_TIMEOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MissedReason {}
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set as the default value when a call was
+ * not missed.
+ */
+ public static final long MISSED_REASON_NOT_MISSED = 0;
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is
+ * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by
+ * system because an ongoing emergency call.
+ */
+ public static final long AUTO_MISSED_EMERGENCY_CALL = 1 << 0;
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is
+ * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by
+ * system because the system cannot support any more ringing calls.
+ */
+ public static final long AUTO_MISSED_MAXIMUM_RINGING = 1 << 1;
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is
+ * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by
+ * system because the system cannot support any more dialing calls.
+ */
+ public static final long AUTO_MISSED_MAXIMUM_DIALING = 1 << 2;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * the call was missed just because user didn't answer it.
+ */
+ public static final long USER_MISSED_NO_ANSWER = 1 << 16;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call rang for a short period of time.
+ */
+ public static final long USER_MISSED_SHORT_RING = 1 << 17;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, when this call
+ * rings less than this defined time in millisecond, set
+ * {@link CallLog.Calls#USER_MISSED_SHORT_RING} bit.
+ * @hide
+ */
+ public static final long SHORT_RING_THRESHOLD = 5000L;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call is silenced because the phone is in 'do not disturb mode'.
+ */
+ public static final long USER_MISSED_DND_MODE = 1 << 18;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call rings with a low ring volume.
+ */
+ public static final long USER_MISSED_LOW_RING_VOLUME = 1 << 19;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, when this call
+ * rings in volume less than this defined volume threshold, set
+ * {@link CallLog.Calls#USER_MISSED_LOW_RING_VOLUME} bit.
+ * @hide
+ */
+ public static final int LOW_RING_VOLUME = 0;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE} set this bit when
+ * this call rings without vibration.
+ */
+ public static final long USER_MISSED_NO_VIBRATE = 1 << 20;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call is silenced by the call screening service.
+ */
+ public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 1 << 21;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * the call filters timed out.
+ */
+ public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22;
+
+ /**
+ * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE},
+ * indicates factors which may have lead the user to miss the call.
+ * <P>Type: INTEGER</P>
+ *
+ * <p>
+ * There are two main cases. Auto missed cases and user missed cases. Default value is:
+ * <ul>
+ * <li>{@link CallLog.Calls#MISSED_REASON_NOT_MISSED}</li>
+ * </ul>
+ * </p>
+ * <P>
+ * Auto missed cases are those where a call was missed because it was not possible for the
+ * incoming call to be presented to the user at all. Possible values are:
+ * <ul>
+ * <li>{@link CallLog.Calls#AUTO_MISSED_EMERGENCY_CALL}</li>
+ * <li>{@link CallLog.Calls#AUTO_MISSED_MAXIMUM_RINGING}</li>
+ * <li>{@link CallLog.Calls#AUTO_MISSED_MAXIMUM_DIALING}</li>
+ * </ul>
+ * </P>
+ * <P>
+ * User missed cases are those where the incoming call was presented to the user, but
+ * factors such as a low ringing volume may have contributed to the call being missed.
+ * Following bits can be set to indicate possible reasons for this:
+ * <ul>
+ * <li>{@link CallLog.Calls#USER_MISSED_SHORT_RING}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_DND_MODE}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_LOW_RING_VOLUME}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_NO_VIBRATE}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_CALL_SCREENING_SERVICE_SILENCED}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_CALL_FILTERS_TIMEOUT}</li>
+ * </ul>
+ * </P>
+ */
+ public static final String MISSED_REASON = "missed_reason";
+
/**
* Adds a call to the call log.
*
@@ -635,12 +776,13 @@
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, int features,
PhoneAccountHandle accountHandle,
- long start, int duration, Long dataUsage) {
+ long start, int duration, Long dataUsage, long missedReason) {
return addCall(ci, context, number, "" /* postDialDigits */, "" /* viaNumber */,
presentation, callType, features, accountHandle, start, duration,
dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */,
false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */,
- null /* callScreeningAppName */, null /* callScreeningComponentName */);
+ null /* callScreeningAppName */, null /* callScreeningComponentName */,
+ missedReason);
}
@@ -675,12 +817,13 @@
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, String viaNumber, int presentation, int callType,
int features, PhoneAccountHandle accountHandle, long start, int duration,
- Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo) {
+ Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
+ long missedReason) {
return addCall(ci, context, number, postDialDigits, viaNumber, presentation, callType,
features, accountHandle, start, duration, dataUsage, addForAllUsers,
userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED
/* callBlockReason */, null /* callScreeningAppName */,
- null /* callScreeningComponentName */);
+ null /* callScreeningComponentName */, missedReason);
}
/**
@@ -714,6 +857,7 @@
* @param callBlockReason The reason why the call is blocked.
* @param callScreeningAppName The call screening application name which block the call.
* @param callScreeningComponentName The call screening component name which block the call.
+ * @param missedReason The encoded missed information of the call.
*
* @result The URI of the call log entry belonging to the user that made or received this
* call. This could be of the shadow provider. Do not return it to non-system apps,
@@ -726,7 +870,7 @@
int features, PhoneAccountHandle accountHandle, long start, int duration,
Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
boolean isRead, int callBlockReason, CharSequence callScreeningAppName,
- String callScreeningComponentName) {
+ String callScreeningComponentName, long missedReason) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
number, userToBeInsertedTo, addForAllUsers));
@@ -779,6 +923,7 @@
values.put(BLOCK_REASON, callBlockReason);
values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName));
values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
+ values.put(MISSED_REASON, Long.valueOf(missedReason));
if ((ci != null) && (ci.getContactId() > 0)) {
// Update usage information for the number associated with the contact ID.
@@ -1114,5 +1259,19 @@
}
return countryIso;
}
+
+ /**
+ * Check if the missedReason code indicate that the call was user missed or automatically
+ * rejected by system.
+ *
+ * @param missedReason
+ * The result is true if the call was user missed, false if the call was automatically
+ * rejected by system.
+ *
+ * @hide
+ */
+ public static boolean isUserMissed(long missedReason) {
+ return missedReason >= (USER_MISSED_NO_ANSWER);
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a4c8114..9c41886 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8380,18 +8380,19 @@
public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled";
/**
- * Whether the panic button (emergency sos) gesture should be enabled.
+ * Whether the emergency gesture should be enabled.
*
* @hide
*/
- public static final String PANIC_GESTURE_ENABLED = "panic_gesture_enabled";
+ public static final String EMERGENCY_GESTURE_ENABLED = "emergency_gesture_enabled";
/**
- * Whether the panic button (emergency sos) sound should be enabled.
+ * Whether the emergency gesture sound should be enabled.
*
* @hide
*/
- public static final String PANIC_SOUND_ENABLED = "panic_sound_enabled";
+ public static final String EMERGENCY_GESTURE_SOUND_ENABLED =
+ "emergency_gesture_sound_enabled";
/**
* Whether the camera launch gesture to double tap the power button when the screen is off
@@ -9914,13 +9915,19 @@
"hdmi_control_auto_device_off_enabled";
/**
- * Property to decide which devices the playback device can send a <Standby> message to upon
- * going to sleep. Supported values are:
+ * Property to decide which devices the playback device can send a <Standby> message to
+ * upon going to sleep. It additionally controls whether a playback device attempts to turn
+ * on the connected Audio system when waking up. Supported values are:
* <ul>
- * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_TO_TV} to TV only.</li>
- * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_BROADCAST} to all devices in the
- * network.</li>
- * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_NONE} no <Standby> message sent.</li>
+ * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_TO_TV} Upon going to sleep, device
+ * sends {@code <Standby>} to TV only. Upon waking up, device does not turn on the Audio
+ * system via {@code <System Audio Mode Request>}.</li>
+ * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_BROADCAST} Upon going to sleep,
+ * device sends {@code <Standby>} to all devices in the network. Upon waking up, device
+ * attempts to turn on the Audio system via {@code <System Audio Mode Request>}.</li>
+ * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_NONE} Upon going to sleep, device
+ * does not send any {@code <Standby>} message. Upon waking up, device does not turn on the
+ * Audio system via {@code <System Audio Mode Request>}.</li>
* </ul>
*
* @hide
@@ -11881,21 +11888,6 @@
public static final String POWER_MANAGER_CONSTANTS = "power_manager_constants";
/**
- * Job scheduler QuotaController specific settings.
- * This is encoded as a key=value list, separated by commas. Ex:
- *
- * "max_job_count_working=5,max_job_count_rare=2"
- *
- * <p>
- * Type: string
- *
- * @hide
- * @see com.android.server.job.JobSchedulerService.Constants
- */
- public static final String JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS =
- "job_scheduler_quota_controller_constants";
-
- /**
* ShortcutManager specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 6bd376a..c5277ee 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -206,8 +206,8 @@
case MSG_SUBSCRIBE: {
final SubscribeMessage sMsg = (SubscribeMessage) msg.obj;
- final SubscriberProxy proxy = new SubscriberProxy(false, mToken,
- sMsg.mSubscriber);
+ final SubscriberProxy proxy = new SubscriberProxy(
+ ControlsProviderService.this, false, mToken, sMsg.mSubscriber);
ControlsProviderService.this.createPublisherFor(sMsg.mControlIds)
.subscribe(proxy);
@@ -251,6 +251,7 @@
private IBinder mToken;
private IControlsSubscriber mCs;
private boolean mEnforceStateless;
+ private Context mContext;
SubscriberProxy(boolean enforceStateless, IBinder token, IControlsSubscriber cs) {
mEnforceStateless = enforceStateless;
@@ -258,6 +259,12 @@
mCs = cs;
}
+ SubscriberProxy(Context context, boolean enforceStateless, IBinder token,
+ IControlsSubscriber cs) {
+ this(enforceStateless, token, cs);
+ mContext = context;
+ }
+
public void onSubscribe(Subscription subscription) {
try {
mCs.onSubscribe(mToken, new SubscriptionAdapter(subscription));
@@ -273,6 +280,9 @@
+ "Control.StatelessBuilder() to build the control.");
control = new Control.StatelessBuilder(control).build();
}
+ if (mContext != null) {
+ control.getControlTemplate().prepareTemplateForBinder(mContext);
+ }
mCs.onNext(mToken, control);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java
index e592fad..3902d6a 100644
--- a/core/java/android/service/controls/templates/ControlTemplate.java
+++ b/core/java/android/service/controls/templates/ControlTemplate.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.Bundle;
import android.service.controls.Control;
import android.service.controls.actions.ControlAction;
@@ -78,6 +79,7 @@
TYPE_NO_TEMPLATE,
TYPE_TOGGLE,
TYPE_RANGE,
+ TYPE_THUMBNAIL,
TYPE_TOGGLE_RANGE,
TYPE_TEMPERATURE,
TYPE_STATELESS
@@ -105,6 +107,11 @@
public static final @TemplateType int TYPE_RANGE = 2;
/**
+ * Type identifier of {@link ThumbnailTemplate}.
+ */
+ public static final @TemplateType int TYPE_THUMBNAIL = 3;
+
+ /**
* Type identifier of {@link ToggleRangeTemplate}.
*/
public static final @TemplateType int TYPE_TOGGLE_RANGE = 6;
@@ -169,6 +176,13 @@
}
/**
+ * Call to prepare values for Binder transport.
+ *
+ * @hide
+ */
+ public void prepareTemplateForBinder(@NonNull Context context) {}
+
+ /**
*
* @param bundle
* @return
@@ -187,6 +201,8 @@
return new ToggleTemplate(bundle);
case TYPE_RANGE:
return new RangeTemplate(bundle);
+ case TYPE_THUMBNAIL:
+ return new ThumbnailTemplate(bundle);
case TYPE_TOGGLE_RANGE:
return new ToggleRangeTemplate(bundle);
case TYPE_TEMPERATURE:
diff --git a/core/java/android/service/controls/templates/ThumbnailTemplate.java b/core/java/android/service/controls/templates/ThumbnailTemplate.java
new file mode 100644
index 0000000..a7c481e
--- /dev/null
+++ b/core/java/android/service/controls/templates/ThumbnailTemplate.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.controls.templates;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.service.controls.Control;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+/**
+ * A template for a {@link Control} that displays an image.
+ */
+public final class ThumbnailTemplate extends ControlTemplate {
+
+ private static final @TemplateType int TYPE = TYPE_THUMBNAIL;
+ private static final String KEY_ICON = "key_icon";
+ private static final String KEY_ACTIVE = "key_active";
+ private static final String KEY_CONTENT_DESCRIPTION = "key_content_description";
+
+ private final boolean mActive;
+ private final @NonNull Icon mThumbnail;
+ private final @NonNull CharSequence mContentDescription;
+
+ /**
+ * @param templateId the identifier for this template object
+ * @param active whether the image corresponds to an active (live) stream.
+ * @param thumbnail an image to display on the {@link Control}
+ * @param contentDescription a description of the image for accessibility.
+ */
+ public ThumbnailTemplate(
+ @NonNull String templateId,
+ boolean active,
+ @NonNull Icon thumbnail,
+ @NonNull CharSequence contentDescription) {
+ super(templateId);
+ Preconditions.checkNotNull(thumbnail);
+ Preconditions.checkNotNull(contentDescription);
+ mActive = active;
+ mThumbnail = thumbnail;
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * @param b
+ * @hide
+ */
+ ThumbnailTemplate(Bundle b) {
+ super(b);
+ mActive = b.getBoolean(KEY_ACTIVE);
+ mThumbnail = b.getParcelable(KEY_ICON);
+ mContentDescription = b.getCharSequence(KEY_CONTENT_DESCRIPTION, "");
+ }
+
+ /*
+ * @return {@code true} if the thumbnail corresponds to an active (live) stream.
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * The {@link Icon} (image) displayed by this template.
+ */
+ @NonNull
+ public Icon getThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * The description of the image returned by {@link ThumbnailTemplate#getThumbnail()}
+ */
+ @NonNull
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * @return {@link ControlTemplate#TYPE_THUMBNAIL}
+ */
+ @Override
+ public int getTemplateType() {
+ return TYPE;
+ }
+
+ /**
+ * Rescales the image down if necessary (in the case of a Bitmap).
+ *
+ * @hide
+ */
+ @Override
+ public void prepareTemplateForBinder(@NonNull Context context) {
+ int width = context.getResources()
+ .getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_width);
+ int height = context.getResources()
+ .getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_height);
+ rescaleThumbnail(width, height);
+ }
+
+ private void rescaleThumbnail(int width, int height) {
+ mThumbnail.scaleDownIfNecessary(width, height);
+ }
+
+ /**
+ * @return
+ * @hide
+ */
+ @Override
+ @NonNull
+ Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putBoolean(KEY_ACTIVE, mActive);
+ b.putObject(KEY_ICON, mThumbnail);
+ b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription);
+ return b;
+ }
+}
diff --git a/core/java/android/text/Editable.java b/core/java/android/text/Editable.java
index 3396bce..a942f6c 100644
--- a/core/java/android/text/Editable.java
+++ b/core/java/android/text/Editable.java
@@ -137,7 +137,7 @@
}
/**
- * Returns a new SpannedStringBuilder from the specified
+ * Returns a new SpannableStringBuilder from the specified
* CharSequence. You can override this to provide
* a different kind of Spanned.
*/
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index ee98b65..a7d20b5 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -64,7 +64,7 @@
private Exception mLastWtf;
// Layout of event log entry received from Android logger.
- // see system/core/liblog/include/log/log_read.h
+ // see system/logging/liblog/include/log/log_read.h
private static final int LENGTH_OFFSET = 0;
private static final int HEADER_SIZE_OFFSET = 2;
private static final int PROCESS_OFFSET = 4;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 43a8992..d226f60 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -755,6 +755,8 @@
/**
* Holds the WM lock for the specified amount of milliseconds.
* Intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
*/
- void holdLock(in int durationMs);
+ void holdLock(in IBinder token, in int durationMs);
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 12ea936..7261765 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -21,7 +21,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -44,24 +43,16 @@
public class NotificationHeaderView extends ViewGroup {
private final int mChildMinWidth;
private final int mContentEndMargin;
- private final int mGravity;
- private View mAppName;
- private View mHeaderText;
- private View mSecondaryHeaderText;
private OnClickListener mExpandClickListener;
- private OnClickListener mFeedbackListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private NotificationTopLineView mTopLineView;
private NotificationExpandButton mExpandButton;
private CachingIconView mIcon;
- private View mProfileBadge;
- private View mFeedbackIcon;
- private boolean mShowWorkBadgeAtEnd;
private int mHeaderTextMarginEnd;
private Drawable mBackground;
private boolean mEntireHeaderClickable;
private boolean mExpandOnlyOnButton;
private boolean mAcceptAllTouches;
- private int mTotalWidth;
ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
@@ -93,23 +84,14 @@
mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
-
- int[] attrIds = {android.R.attr.gravity};
- TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
- mGravity = ta.getInt(0, 0);
- ta.recycle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mAppName = findViewById(R.id.app_name_text);
- mHeaderText = findViewById(R.id.header_text);
- mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
- mExpandButton = findViewById(R.id.expand_button);
mIcon = findViewById(R.id.icon);
- mProfileBadge = findViewById(R.id.profile_badge);
- mFeedbackIcon = findViewById(R.id.feedback);
+ mTopLineView = findViewById(R.id.notification_top_line);
+ mExpandButton = findViewById(R.id.expand_button);
setClipToPadding(false);
}
@@ -136,7 +118,7 @@
lp.topMargin + lp.bottomMargin, lp.height);
child.measure(childWidthSpec, childHeightSpec);
// Icons that should go at the end
- if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) {
+ if (child == mExpandButton) {
iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
} else {
totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -147,19 +129,10 @@
int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth);
if (totalWidth > givenWidth - endMargin) {
int overFlow = totalWidth - givenWidth + endMargin;
- // We are overflowing, lets shrink the app name first
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
+ // We are overflowing; shrink the top line
+ shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mTopLineView,
mChildMinWidth);
-
- // still overflowing, we shrink the header text
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
-
- // still overflowing, finally we shrink the secondary header text
- shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
- 0);
}
- totalWidth += getPaddingEnd();
- mTotalWidth = Math.min(totalWidth, givenWidth);
setMeasuredDimension(givenWidth, givenHeight);
}
@@ -180,10 +153,6 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingStart();
int end = getMeasuredWidth();
- final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
- if (centerAligned) {
- left += getMeasuredWidth() / 2 - mTotalWidth / 2;
- }
int childCount = getChildCount();
int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
for (int i = 0; i < childCount; i++) {
@@ -198,7 +167,7 @@
int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
int bottom = top + childHeight;
// Icons that should go at the end
- if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) {
+ if (child == mExpandButton) {
if (end == getMeasuredWidth()) {
layoutRight = end - mContentEndMargin;
} else {
@@ -266,7 +235,7 @@
}
private void updateTouchListener() {
- if (mExpandClickListener == null && mFeedbackListener == null) {
+ if (mExpandClickListener == null) {
setOnTouchListener(null);
return;
}
@@ -274,15 +243,6 @@
mTouchListener.bindTouchRects();
}
- /**
- * Sets onclick listener for feedback icon.
- */
- public void setFeedbackOnClickListener(OnClickListener l) {
- mFeedbackListener = l;
- mFeedbackIcon.setOnClickListener(mFeedbackListener);
- updateTouchListener();
- }
-
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
mExpandClickListener = l;
@@ -291,16 +251,6 @@
}
/**
- * Sets whether or not the work badge appears at the end of the NotificationHeaderView.
- * The expand button will always be closer to the end.
- */
- public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
- if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
- mShowWorkBadgeAtEnd = showWorkBadgeAtEnd;
- }
- }
-
- /**
* Sets the margin end for the text portion of the header, excluding right-aligned elements
* @param headerTextMarginEnd margin size
*/
@@ -327,7 +277,6 @@
private final ArrayList<Rect> mTouchRects = new ArrayList<>();
private Rect mExpandButtonRect;
- private Rect mFeedbackRect;
private int mTouchSlop;
private boolean mTrackGesture;
private float mDownX;
@@ -340,7 +289,6 @@
mTouchRects.clear();
addRectAroundView(mIcon);
mExpandButtonRect = addRectAroundView(mExpandButton);
- mFeedbackRect = addRectAroundView(mFeedbackIcon);
addWidthRect();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -401,11 +349,11 @@
break;
case MotionEvent.ACTION_UP:
if (mTrackGesture) {
- if (mFeedbackIcon.isVisibleToUser()
- && (mFeedbackRect.contains((int) x, (int) y)
- || mFeedbackRect.contains((int) mDownX, (int) mDownY))) {
- mFeedbackIcon.performClick();
- return true;
+ float topLineX = mTopLineView.getX();
+ float topLineY = mTopLineView.getY();
+ if (mTopLineView.onTouchUp(x - topLineX, y - topLineY,
+ mDownX - topLineX, mDownY - topLineY)) {
+ break;
}
mExpandButton.performClick();
}
@@ -427,7 +375,9 @@
return true;
}
}
- return false;
+ float topLineX = x - mTopLineView.getX();
+ float topLineY = y - mTopLineView.getY();
+ return mTopLineView.isInTouchRect(topLineX, topLineY);
}
}
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
new file mode 100644
index 0000000..9443ccf
--- /dev/null
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The top line of content in a notification view.
+ * This includes the text views and badges but excludes the icon and the expander.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationTopLineView extends ViewGroup {
+ private final int mChildMinWidth;
+ private final int mContentEndMargin;
+ private View mAppName;
+ private View mHeaderText;
+ private View mSecondaryHeaderText;
+ private OnClickListener mFeedbackListener;
+ private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private View mProfileBadge;
+ private View mFeedbackIcon;
+ private int mHeaderTextMarginEnd;
+ private List<View> mIconsAtEnd;
+
+ public NotificationTopLineView(Context context) {
+ this(context, null);
+ }
+
+ public NotificationTopLineView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationTopLineView(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationTopLineView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ Resources res = getResources();
+ mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppName = findViewById(R.id.app_name_text);
+ mHeaderText = findViewById(R.id.header_text);
+ mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+ mProfileBadge = findViewById(R.id.profile_badge);
+ mFeedbackIcon = findViewById(R.id.feedback);
+ mIconsAtEnd = Arrays.asList(mProfileBadge, mFeedbackIcon);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
+ MeasureSpec.AT_MOST);
+ int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
+ MeasureSpec.AT_MOST);
+ int totalWidth = getPaddingStart();
+ int iconWidth = getPaddingEnd();
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ // We'll give it the rest of the space in the end
+ continue;
+ }
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+ lp.leftMargin + lp.rightMargin, lp.width);
+ int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+ lp.topMargin + lp.bottomMargin, lp.height);
+ child.measure(childWidthSpec, childHeightSpec);
+ // Icons that should go at the end
+ if (mIconsAtEnd.contains(child)) {
+ iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ } else {
+ totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ }
+ }
+
+ // Ensure that there is at least enough space for the icons
+ int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth);
+ if (totalWidth > givenWidth - endMargin) {
+ int overFlow = totalWidth - givenWidth + endMargin;
+ // We are overflowing, lets shrink the app name first
+ overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
+ mChildMinWidth);
+
+ // still overflowing, we shrink the header text
+ overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
+
+ // still overflowing, finally we shrink the secondary header text
+ shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
+ 0);
+ }
+ setMeasuredDimension(givenWidth, givenHeight);
+ }
+
+ private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
+ int minimumWidth) {
+ final int oldWidth = targetView.getMeasuredWidth();
+ if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
+ // we're still too big
+ int newSize = Math.max(minimumWidth, oldWidth - overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ targetView.measure(childWidthSpec, heightSpec);
+ overFlow -= oldWidth - newSize;
+ }
+ return overFlow;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int left = getPaddingStart();
+ int end = getMeasuredWidth();
+ int childCount = getChildCount();
+ int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ int childHeight = child.getMeasuredHeight();
+ MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
+ int layoutLeft;
+ int layoutRight;
+ int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
+ int bottom = top + childHeight;
+ // Icons that should go at the end
+ if (mIconsAtEnd.contains(child)) {
+ if (end == getMeasuredWidth()) {
+ layoutRight = end - mContentEndMargin;
+ } else {
+ layoutRight = end - params.getMarginEnd();
+ }
+ layoutLeft = layoutRight - child.getMeasuredWidth();
+ end = layoutLeft - params.getMarginStart();
+ } else {
+ left += params.getMarginStart();
+ int right = left + child.getMeasuredWidth();
+ layoutLeft = left;
+ layoutRight = right;
+ left = right + params.getMarginEnd();
+ }
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ int ltrLeft = layoutLeft;
+ layoutLeft = getWidth() - layoutRight;
+ layoutRight = getWidth() - ltrLeft;
+ }
+ child.layout(layoutLeft, top, layoutRight, bottom);
+ }
+ updateTouchListener();
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+ private void updateTouchListener() {
+ if (mFeedbackListener == null) {
+ setOnTouchListener(null);
+ return;
+ }
+ setOnTouchListener(mTouchListener);
+ mTouchListener.bindTouchRects();
+ }
+
+ /**
+ * Sets onclick listener for feedback icon.
+ */
+ public void setFeedbackOnClickListener(OnClickListener l) {
+ mFeedbackListener = l;
+ mFeedbackIcon.setOnClickListener(mFeedbackListener);
+ updateTouchListener();
+ }
+
+ /**
+ * Sets the margin end for the text portion of the header, excluding right-aligned elements
+ *
+ * @param headerTextMarginEnd margin size
+ */
+ public void setHeaderTextMarginEnd(int headerTextMarginEnd) {
+ if (mHeaderTextMarginEnd != headerTextMarginEnd) {
+ mHeaderTextMarginEnd = headerTextMarginEnd;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Get the current margin end value for the header text
+ *
+ * @return margin size
+ */
+ public int getHeaderTextMarginEnd() {
+ return mHeaderTextMarginEnd;
+ }
+
+ private class HeaderTouchListener implements OnTouchListener {
+
+ private Rect mFeedbackRect;
+ private int mTouchSlop;
+ private boolean mTrackGesture;
+ private float mDownX;
+ private float mDownY;
+
+ HeaderTouchListener() {
+ }
+
+ public void bindTouchRects() {
+ mFeedbackRect = getRectAroundView(mFeedbackIcon);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ private Rect getRectAroundView(View view) {
+ float size = 48 * getResources().getDisplayMetrics().density;
+ float width = Math.max(size, view.getWidth());
+ float height = Math.max(size, view.getHeight());
+ final Rect r = new Rect();
+ if (view.getVisibility() == GONE) {
+ view = getFirstChildNotGone();
+ r.left = (int) (view.getLeft() - width / 2.0f);
+ } else {
+ r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f);
+ }
+ r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f);
+ r.bottom = (int) (r.top + height);
+ r.right = (int) (r.left + width);
+ return r;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mTrackGesture = false;
+ if (isInside(x, y)) {
+ mDownX = x;
+ mDownY = y;
+ mTrackGesture = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackGesture) {
+ if (Math.abs(mDownX - x) > mTouchSlop
+ || Math.abs(mDownY - y) > mTouchSlop) {
+ mTrackGesture = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackGesture && onTouchUp(x, y, mDownX, mDownY)) {
+ return true;
+ }
+ break;
+ }
+ return mTrackGesture;
+ }
+
+ private boolean onTouchUp(float upX, float upY, float downX, float downY) {
+ if (mFeedbackIcon.isVisibleToUser()
+ && (mFeedbackRect.contains((int) upX, (int) upY)
+ || mFeedbackRect.contains((int) downX, (int) downY))) {
+ mFeedbackIcon.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInside(float x, float y) {
+ return mFeedbackRect.contains((int) x, (int) y);
+ }
+ }
+
+ private View getFirstChildNotGone() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ return child;
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /**
+ * Determine if the given point is touching an active part of the top line.
+ */
+ public boolean isInTouchRect(float x, float y) {
+ if (mFeedbackListener == null) {
+ return false;
+ }
+ return mTouchListener.isInside(x, y);
+ }
+
+ /**
+ * Perform a click on an active part of the top line, if touching.
+ */
+ public boolean onTouchUp(float upX, float upY, float downX, float downY) {
+ if (mFeedbackListener == null) {
+ return false;
+ }
+ return mTouchListener.onTouchUp(upX, upY, downX, downY);
+ }
+}
diff --git a/core/java/android/view/OnReceiveContentCallback.java b/core/java/android/view/OnReceiveContentCallback.java
index a217ff6..d74938c 100644
--- a/core/java/android/view/OnReceiveContentCallback.java
+++ b/core/java/android/view/OnReceiveContentCallback.java
@@ -19,9 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.content.ClipData;
-import android.content.ClipDescription;
import android.net.Uri;
import android.os.Bundle;
@@ -30,11 +28,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-import java.util.Set;
/**
- * Callback for apps to implement handling for insertion of content. "Content" here refers to both
- * text and non-text (plain/styled text, HTML, images, videos, audio files, etc).
+ * Callback for apps to implement handling for insertion of content. Content may be both text and
+ * non-text (plain/styled text, HTML, images, videos, audio files, etc).
*
* <p>This callback can be attached to different types of UI components using
* {@link View#setOnReceiveContentCallback}.
@@ -45,32 +42,38 @@
*
* <p>Example implementation:<br>
* <pre class="prettyprint">
+ * // (1) Define the callback
* public class MyOnReceiveContentCallback implements OnReceiveContentCallback<TextView> {
- *
- * private static final Set<String> SUPPORTED_MIME_TYPES = Collections.unmodifiableSet(
+ * public static final Set<String> MIME_TYPES = Collections.unmodifiableSet(
* Set.of("image/*", "video/*"));
*
- * @NonNull
- * @Override
- * public Set<String> getSupportedMimeTypes() {
- * return SUPPORTED_MIME_TYPES;
- * }
- *
* @Override
* public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) {
* // ... app-specific logic to handle the content in the payload ...
* }
* }
+ *
+ * // (2) Register the callback
+ * public class MyActivity extends Activity {
+ * @Override
+ * public void onCreate(Bundle savedInstanceState) {
+ * // ...
+ *
+ * EditText myInput = findViewById(R.id.my_input);
+ * myInput.setOnReceiveContentCallback(
+ * MyOnReceiveContentCallback.MIME_TYPES,
+ * new MyOnReceiveContentCallback());
+ * }
* </pre>
*
- * @param <T> The type of {@link View} with which this receiver can be associated.
+ * @param <T> The type of {@link View} with which this callback can be associated.
*/
public interface OnReceiveContentCallback<T extends View> {
/**
* Receive the given content.
*
- * <p>This function will only be invoked if the MIME type of the content is in the set of
- * types returned by {@link #getSupportedMimeTypes}.
+ * <p>This method is only invoked for content whose MIME type matches a type specified via
+ * {@link View#setOnReceiveContentCallback}.
*
* <p>For text, if the view has a selection, the selection should be overwritten by the clip; if
* there's no selection, this method should insert the content at the current cursor position.
@@ -81,54 +84,14 @@
* @param view The view where the content insertion was requested.
* @param payload The content to insert and related metadata.
*
- * @return Returns true if some or all of the content is accepted for insertion. If accepted,
- * actual insertion may be handled asynchronously in the background and may or may not result in
- * successful insertion. For example, the app may not end up inserting an accepted item if it
+ * @return Returns true if the content was handled in some way, false otherwise. Actual
+ * insertion may be processed asynchronously in the background and may or may not succeed even
+ * if this method returns true. For example, an app may not end up inserting an item if it
* exceeds the app's size limit for that type of content.
*/
boolean onReceiveContent(@NonNull T view, @NonNull Payload payload);
/**
- * Returns the MIME types that can be handled by this callback.
- *
- * <p>The {@link #onReceiveContent} callback method will only be invoked if the MIME type of the
- * content is in the set of supported types returned here.
- *
- * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the
- * keyboard, etc) may use this function to conditionally alter their behavior. For example, the
- * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has
- * a {@link OnReceiveContentCallback} set and the MIME types returned from this function don't
- * include "image/gif".
- *
- * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
- * MIME types. As a result, you should always write your MIME types with lower case letters, or
- * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower
- * case.</em>
- *
- * @param view The target view.
- * @return An immutable set with the MIME types supported by this callback. The returned MIME
- * types may contain wildcards such as "text/*", "image/*", etc.
- */
- @SuppressLint("CallbackMethodName")
- @NonNull
- Set<String> getSupportedMimeTypes(@NonNull T view);
-
- /**
- * Returns true if at least one of the MIME types of the given clip is
- * {@link #getSupportedMimeTypes supported} by this receiver.
- *
- * @hide
- */
- default boolean supports(@NonNull T view, @NonNull ClipDescription description) {
- for (String supportedMimeType : getSupportedMimeTypes(view)) {
- if (description.hasMimeType(supportedMimeType)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Holds all the relevant data for a request to {@link OnReceiveContentCallback}.
*/
final class Payload {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 667f0b9..ac628e1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -47,6 +47,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AutofillOptions;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -80,7 +81,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.net.Uri;
import android.os.Build;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -144,6 +144,7 @@
import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
import com.android.internal.view.ScrollCaptureInternal;
import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
@@ -5244,7 +5245,9 @@
int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE;
@Nullable
- private OnReceiveContentCallback<? extends View> mOnReceiveContentCallback;
+ private String[] mOnReceiveContentMimeTypes;
+ @Nullable
+ private OnReceiveContentCallback mOnReceiveContentCallback;
/**
* Simple constructor to use when creating a view from code.
@@ -9002,36 +9005,78 @@
}
/**
- * Returns the callback used for handling insertion of content into this view. See
- * {@link #setOnReceiveContentCallback} for more info.
- *
- * @return The callback for handling insertion of content. Returns null if no callback has been
- * {@link #setOnReceiveContentCallback set}.
- */
- @Nullable
- public OnReceiveContentCallback<? extends View> getOnReceiveContentCallback() {
- return mOnReceiveContentCallback;
- }
-
- /**
* Sets the callback to handle insertion of content into this view.
*
* <p>Depending on the view, this callback may be invoked for scenarios such as content
* insertion from the IME, Autofill, etc.
*
- * <p>The callback will only be invoked if the MIME type of the content is
- * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback.
- * If the content type is not supported by the callback, the default platform handling will be
- * executed instead.
+ * <p>This callback is only invoked for content whose MIME type matches a type specified via
+ * the {code mimeTypes} parameter. If the MIME type is not supported by the callback, the
+ * default platform handling will be executed instead (no-op for the default {@link View}).
*
+ * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
+ * MIME types. As a result, you should always write your MIME types with lower case letters, or
+ * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower
+ * case.</em>
+ *
+ * @param mimeTypes The type of content for which the callback should be invoked. This may use
+ * wildcards such as "text/*", "image/*", etc. This must not be null or empty if a non-null
+ * callback is passed in.
* @param callback The callback to use. This can be null to reset to the default behavior.
*/
- public void setOnReceiveContentCallback(
- @Nullable OnReceiveContentCallback<? extends View> callback) {
+ @SuppressWarnings("rawtypes")
+ public void setOnReceiveContentCallback(@Nullable String[] mimeTypes,
+ @Nullable OnReceiveContentCallback callback) {
+ if (callback != null) {
+ Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
+ "When the callback is set, MIME types must also be set");
+ }
+ mOnReceiveContentMimeTypes = mimeTypes;
mOnReceiveContentCallback = callback;
}
/**
+ * Receives the given content. The default implementation invokes the callback set via
+ * {@link #setOnReceiveContentCallback}. If no callback is set or if the callback does not
+ * support the given content (based on the MIME type), returns false.
+ *
+ * @param payload The content to insert and related metadata.
+ *
+ * @return Returns true if the content was handled in some way, false otherwise. Actual
+ * insertion may be processed asynchronously in the background and may or may not succeed even
+ * if this method returns true. For example, an app may not end up inserting an item if it
+ * exceeds the app's size limit for that type of content.
+ */
+ public boolean onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) {
+ ClipDescription description = payload.getClip().getDescription();
+ if (mOnReceiveContentCallback != null && mOnReceiveContentMimeTypes != null
+ && description.hasMimeType(mOnReceiveContentMimeTypes)) {
+ return mOnReceiveContentCallback.onReceiveContent(this, payload);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the MIME types that can be handled by {@link #onReceiveContent} for this view, as
+ * configured via {@link #setOnReceiveContentCallback}. By default returns null.
+ *
+ * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the
+ * keyboard, etc) may use this function to conditionally alter their behavior. For example, the
+ * soft keyboard may choose to hide its UI for inserting GIFs for a particular input field if
+ * the MIME types returned here for that field don't include "image/gif".
+ *
+ * <p>Note: Comparisons of MIME types should be performed using utilities such as
+ * {@link ClipDescription#compareMimeTypes} rather than simple string equality, in order to
+ * correctly handle patterns (e.g. "text/*").
+ *
+ * @return The MIME types supported by {@link #onReceiveContent} for this view. The returned
+ * MIME types may contain wildcards such as "text/*", "image/*", etc.
+ */
+ public @Nullable String[] getOnReceiveContentMimeTypes() {
+ return mOnReceiveContentMimeTypes;
+ }
+
+ /**
* Automatically fills the content of this view with the {@code value}.
*
* <p>Views support the Autofill Framework mainly by:
@@ -26444,7 +26489,9 @@
surface.copyFrom(surfaceControl);
IBinder token = null;
try {
- final Canvas canvas = surface.lockCanvas(null);
+ final Canvas canvas = isHardwareAccelerated()
+ ? surface.lockHardwareCanvas()
+ : surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
@@ -26535,7 +26582,9 @@
}
if (mAttachInfo.mDragToken != null) {
try {
- Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ Canvas canvas = isHardwareAccelerated()
+ ? mAttachInfo.mDragSurface.lockHardwareCanvas()
+ : mAttachInfo.mDragSurface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
@@ -28913,33 +28962,6 @@
boolean mUse32BitDrawingCache;
/**
- * For windows that are full-screen but using insets to layout inside
- * of the screen decorations, these are the current insets for the
- * content of the window.
- */
- @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q,
- publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
- final Rect mContentInsets = new Rect();
-
- /**
- * For windows that are full-screen but using insets to layout inside
- * of the screen decorations, these are the current insets for the
- * actual visible parts of the window.
- */
- @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q,
- publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
- final Rect mVisibleInsets = new Rect();
-
- /**
- * For windows that are full-screen but using insets to layout inside
- * of the screen decorations, these are the current insets for the
- * stable system windows.
- */
- @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q,
- publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
- final Rect mStableInsets = new Rect();
-
- /**
* Current caption insets to the display coordinate.
*/
final Rect mCaptionInsets = new Rect();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9bc0770..f1005e91 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1758,7 +1758,6 @@
destroySurface();
}
}
- scheduleConsumeBatchedInputImmediately();
}
@@ -8314,11 +8313,8 @@
@Override
public void onBatchedInputEventPending(int source) {
- // mStopped: There will be no more choreographer callbacks if we are stopped,
- // so we must consume all input immediately to prevent ANR
final boolean unbuffered = mUnbufferedInputDispatch
- || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE
- || mStopped;
+ || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE;
if (unbuffered) {
if (mConsumeBatchedInputScheduled) {
unscheduleConsumeBatchedInput();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2cab32b..d540059 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4069,11 +4069,12 @@
/**
* Holds the WM lock for the specified amount of milliseconds.
* Intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
* @hide
*/
@TestApi
- @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
- default void holdLock(int durationMs) {
+ default void holdLock(IBinder token, int durationMs) {
throw new UnsupportedOperationException();
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 7dfae00..4292a80 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -280,9 +280,9 @@
}
@Override
- public void holdLock(int durationMs) {
+ public void holdLock(IBinder token, int durationMs) {
try {
- WindowManagerGlobal.getWindowManagerService().holdLock(durationMs);
+ WindowManagerGlobal.getWindowManagerService().holdLock(token, durationMs);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index e071113..093dfb4 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -22,6 +22,7 @@
import android.annotation.IntRange;
import android.annotation.Nullable;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -918,23 +919,18 @@
}
/**
- * Default implementation which invokes the target view's {@link OnReceiveContentCallback} if
- * it is {@link View#setOnReceiveContentCallback set} and supports the MIME type of the given
- * content; otherwise, simply returns false.
+ * Default implementation which invokes {@link View#onReceiveContent} on the target view if the
+ * MIME type of the content matches one of the MIME types returned by
+ * {@link View#getOnReceiveContentMimeTypes()}. If the MIME type of the content is not matched,
+ * returns false without any side effects.
*/
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
- @SuppressWarnings("unchecked") final OnReceiveContentCallback<View> receiver =
- (OnReceiveContentCallback<View>) mTargetView.getOnReceiveContentCallback();
- if (receiver == null) {
+ ClipDescription description = inputContentInfo.getDescription();
+ final String[] viewMimeTypes = mTargetView.getOnReceiveContentMimeTypes();
+ if (viewMimeTypes == null || !description.hasMimeType(viewMimeTypes)) {
if (DEBUG) {
- Log.d(TAG, "Can't insert content from IME; no callback");
- }
- return false;
- }
- if (!receiver.supports(mTargetView, inputContentInfo.getDescription())) {
- if (DEBUG) {
- Log.d(TAG, "Can't insert content from IME; callback doesn't support MIME type: "
- + inputContentInfo.getDescription());
+ Log.d(TAG, "Can't insert content from IME; unsupported MIME type: content="
+ + description + ", viewMimeTypes=" + viewMimeTypes);
}
return false;
}
@@ -946,13 +942,13 @@
return false;
}
}
- final ClipData clip = new ClipData(inputContentInfo.getDescription(),
+ final ClipData clip = new ClipData(description,
new ClipData.Item(inputContentInfo.getContentUri()));
final OnReceiveContentCallback.Payload payload =
new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_INPUT_METHOD)
.setLinkUri(inputContentInfo.getLinkUri())
.setExtras(opts)
.build();
- return receiver.onReceiveContent(mTargetView, payload);
+ return mTargetView.onReceiveContent(payload);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3fc0f4e..5280a48 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8747,12 +8747,7 @@
outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
outAttrs.setInitialSurroundingText(mText);
- // If a custom `OnReceiveContentCallback` is set, pass its supported MIME types.
- OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback();
- if (receiver != null) {
- outAttrs.contentMimeTypes = receiver.getSupportedMimeTypes(this)
- .toArray(new String[0]);
- }
+ outAttrs.contentMimeTypes = getOnReceiveContentMimeTypes();
return ic;
}
}
@@ -13735,20 +13730,6 @@
}
/**
- * Returns the callback used for handling insertion of content into this view. See
- * {@link #setOnReceiveContentCallback} for more info.
- *
- * @return The callback for handling insertion of content. Returns null if no callback has been
- * {@link #setOnReceiveContentCallback set}.
- */
- @SuppressWarnings("unchecked")
- @Nullable
- @Override
- public OnReceiveContentCallback<TextView> getOnReceiveContentCallback() {
- return (OnReceiveContentCallback<TextView>) super.getOnReceiveContentCallback();
- }
-
- /**
* Sets the callback to handle insertion of content into this view.
*
* <p>This callback will be invoked for the following scenarios:
@@ -13761,32 +13742,51 @@
* <li>{@link Intent#ACTION_PROCESS_TEXT} replacement
* </ol>
*
- * <p>The callback will only be invoked if the MIME type of the content is
- * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback.
- * If the content type is not supported by the callback, the default platform handling will be
- * executed instead.
+ * <p>This callback is only invoked for content whose MIME type matches a type specified via
+ * the {code mimeTypes} parameter. If the MIME type is not supported by the callback, the
+ * default platform handling will be executed instead (no-op for the default {@link View}).
*
+ * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
+ * MIME types. As a result, you should always write your MIME types with lower case letters, or
+ * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower
+ * case.</em>
+ *
+ * @param mimeTypes The type of content for which the callback should be invoked. This may use
+ * wildcards such as "text/*", "image/*", etc. This must not be null or empty if a non-null
+ * callback is passed in.
* @param callback The callback to use. This can be null to reset to the default behavior.
*/
+ @SuppressWarnings("rawtypes")
@Override
public void setOnReceiveContentCallback(
- @Nullable OnReceiveContentCallback<? extends View> callback) {
- super.setOnReceiveContentCallback(callback);
+ @Nullable String[] mimeTypes,
+ @Nullable OnReceiveContentCallback callback) {
+ super.setOnReceiveContentCallback(mimeTypes, callback);
}
/**
- * Handles the request to insert content using the configured callback or the default callback.
+ * Receives the given content. The default implementation invokes the callback set via
+ * {@link #setOnReceiveContentCallback}. If no callback is set or if the callback does not
+ * support the given content (based on the MIME type), executes the default platform handling
+ * (e.g. coerces content to text if the source is
+ * {@link OnReceiveContentCallback.Payload#SOURCE_CLIPBOARD} and this is an editable
+ * {@link TextView}).
*
- * @hide
+ * @param payload The content to insert and related metadata.
+ *
+ * @return Returns true if the content was handled in some way, false otherwise. Actual
+ * insertion may be processed asynchronously in the background and may or may not succeed even
+ * if this method returns true. For example, an app may not end up inserting an item if it
+ * exceeds the app's size limit for that type of content.
*/
- void onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) {
- OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback();
- ClipDescription description = payload.getClip().getDescription();
- if (receiver != null && receiver.supports(this, description)) {
- receiver.onReceiveContent(this, payload);
+ @Override
+ public boolean onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) {
+ if (super.onReceiveContent(payload)) {
+ return true;
} else if (mEditor != null) {
- mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload);
+ return mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload);
}
+ return false;
}
private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
diff --git a/core/java/android/widget/TextViewOnReceiveContentCallback.java b/core/java/android/widget/TextViewOnReceiveContentCallback.java
index d7c95b7..7ed70ec 100644
--- a/core/java/android/widget/TextViewOnReceiveContentCallback.java
+++ b/core/java/android/widget/TextViewOnReceiveContentCallback.java
@@ -20,12 +20,12 @@
import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT;
import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL;
import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;
import static java.util.Collections.singleton;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -72,16 +72,6 @@
@Nullable private InputConnectionInfo mInputConnectionInfo;
@Nullable private ArraySet<String> mCachedSupportedMimeTypes;
- @SuppressLint("CallbackMethodName")
- @NonNull
- @Override
- public Set<String> getSupportedMimeTypes(@NonNull TextView view) {
- if (!isUsageOfImeCommitContentEnabled(view)) {
- return MIME_TYPES_ALL_TEXT;
- }
- return getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes();
- }
-
@Override
public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
@@ -90,6 +80,11 @@
ClipData clip = payload.getClip();
@Source int source = payload.getSource();
@Flags int flags = payload.getFlags();
+ if (source == SOURCE_INPUT_METHOD) {
+ // InputConnection.commitContent() should only be used for non-text input which is not
+ // supported by the default implementation.
+ return false;
+ }
if (source == SOURCE_AUTOFILL) {
return onReceiveForAutofill(view, clip, flags);
}
@@ -123,7 +118,7 @@
}
}
}
- return true;
+ return didFirst;
}
private static void replaceSelection(@NonNull Editable editable,
@@ -160,7 +155,7 @@
@NonNull ClipData clip, @Flags int flags) {
final CharSequence text = coerceToText(clip, textView.getContext(), flags);
if (text.length() == 0) {
- return true;
+ return false;
}
replaceSelection((Editable) textView.getText(), text);
return true;
@@ -205,7 +200,7 @@
* non-text content.
*/
private static boolean isUsageOfImeCommitContentEnabled(@NonNull View view) {
- if (view.getOnReceiveContentCallback() != null) {
+ if (view.getOnReceiveContentMimeTypes() != null) {
if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
Log.v(LOG_TAG, "Fallback to commitContent disabled (custom callback is set)");
}
@@ -267,6 +262,17 @@
mInputConnectionInfo = null;
}
+ // TODO(b/168253885): Use this to populate the assist structure for Autofill
+
+ /** @hide */
+ @VisibleForTesting
+ public Set<String> getMimeTypes(TextView view) {
+ if (!isUsageOfImeCommitContentEnabled(view)) {
+ return MIME_TYPES_ALL_TEXT;
+ }
+ return getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes();
+ }
+
private Set<String> getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes() {
InputConnectionInfo icInfo = mInputConnectionInfo;
if (icInfo == null) {
@@ -291,7 +297,8 @@
}
/**
- * We want to avoid creating a new set on every invocation of {@link #getSupportedMimeTypes}.
+ * We want to avoid creating a new set on every invocation of
+ * {@link #getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes()}.
* This method will check if the cached set of MIME types matches the data in the given array
* from {@link EditorInfo} or if a new set should be created. The custom logic is needed for
* comparing the data because the set contains the additional "text/*" MIME type.
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 3a84c1f..4a43a43 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -40,7 +40,7 @@
void unregisterTaskOrganizer(ITaskOrganizer organizer);
/** Creates a persistent root task in WM for a particular windowing-mode. */
- ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode);
+ void createRootTask(int displayId, int windowingMode, IBinder launchCookie);
/** Deletes a persistent root task in WM */
boolean deleteRootTask(in WindowContainerToken task);
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 6c739be..ad48a9f 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -23,6 +23,7 @@
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.app.ActivityManager;
+import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
@@ -101,12 +102,18 @@
@BinderThread
public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
- /** Creates a persistent root task in WM for a particular windowing-mode. */
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param launchCookie Launch cookie to associate with the task so that is can be identified
+ * when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
+ */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
@Nullable
- public ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode) {
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
try {
- return mTaskOrganizerController.createRootTask(displayId, windowingMode);
+ mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/jni/android_view_InputEventReceiver.md b/core/jni/android_view_InputEventReceiver.md
new file mode 100644
index 0000000..7df3461
--- /dev/null
+++ b/core/jni/android_view_InputEventReceiver.md
@@ -0,0 +1,49 @@
+# Batched consumption #
+
+Most apps draw once per vsync. Therefore, apps can only respond to 1 input event per frame. If multiple input events come in during the period of 1 vsync, it would be wasteful to deliver them all at once to the app. For this reason, input events are batched to only deliver 1 event per frame to the app.
+
+The batching process works in the following manner:
+
+1. `InputDispatcher` sends an event to the app
+2. The app's `Looper` is notified about the available event.
+3. The `handleEvent` callback is executed. Events are read from fd.
+4. If a batched input event is available, `InputConsumer::hasPendingBatch` returns true. No event is sent to the app at this point.
+5. The app is notified that a batched event is available for consumption, and schedules a runnable via the `Choreographer` to consume it a short time before the next frame
+6. When the scheduled runnable is executed, it doesn't just consume the batched input. It proactively tries to consume everything that has come in to the socket.
+7. The batched event is sent to the app, along with any of the other events that have come in.
+
+Let's discuss the specifics of some of these steps.
+
+## 1. Consuming events in `handleEvent` callback ##
+
+The app is notified about the available event via the `Looper` callback `handleEvent`. When the app's input socket becomes readable (e.g., it has unread events), the looper will execute `handleEvent`. At this point, the app is expected to read in the events that have come in to the socket. The function `handleEvent` will continue to trigger as long as there are unread events in the socket. Thus, the app could choose to read events 1 at a time, or all at once. If there are no more events in the app's socket, handleEvent will no longer execute.
+
+Even though it is perfectly valid for the app to read events 1 at a time, it is more efficient to read them all at once. Therefore, whenever the events are available, the app will try to completely drain the socket.
+
+To consume the events inside `handleEvent`, the app calls `InputConsumer::consume(.., consumeBatches=false, frameTime=-1, ..)`. That is, when `handleEvent` runs, there is no information about the upcoming frameTime, and we dont want to consume the batches because there may be other events that come in before the 'consume batched input' runnable runs.
+
+If a batched event comes in at this point (typically, any MOVE event that has source = TOUCHSCREEN), the `consume` function above would actually return a `NULL` event with status `WOULD_BLOCK`. When this happens, the caller (`NativeInputEventReceiver`) is responsible for checking whether `InputConsumer::hasPendingBatch` is set to true. If so, the caller is responsible for scheduling a runnable to consume these batched events.
+
+## 2. Consuming batched events ##
+
+In the previous section, we learned that the app can read events inside the `handleEvent` callback. The other time when the app reads events is when the 'consume batched input' runnable is executed. This runnable is scheduled via the Choreographer by requesting a `CALLBACK_INPUT` event.
+
+Before the batched events are consumed, the socket is drained once again. This is an optimization.
+
+To consume the events inside 'consume batched input' runnable, the app calls `InputConsumer::consume(.., consumeBatches=true, frameTime=<valid frame time>, ..)`. At this point, the `consume` function will return all batched events up to the `frameTime` point. There may be batched events remaining.
+
+## 3. Key points ##
+
+Some of the behaviours above should be highlighted, because they may be unexpected.
+
+1. Even if events have been read by `InputConsumer`, `consume` will return `NULL` event with status `WOULD_BLOCK` if those events caused a new batch to be started.
+
+2. Events are read from the fd outside of the regular `handleEvent` case, during batched consumption.
+
+3. The function `handleEvent` will always execute as long as there are unread events in the fd
+
+4. The `consume` function is called in 1 of 2 possible ways:
+ - `consumeBatches=false, frameTime=-1`
+ - `consumeBatches=true, frameTime=<valid time>`
+
+ I.e., it is never called with `consumeBatches=true, frameTime=-1`.
diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto
index 37a9f50..2d2c8ac 100644
--- a/core/proto/android/app/enums.proto
+++ b/core/proto/android/app/enums.proto
@@ -210,4 +210,5 @@
APP_OP_PHONE_CALL_MICROPHONE = 100;
APP_OP_PHONE_CALL_CAMERA = 101;
APP_OP_RECORD_AUDIO_HOTWORD = 102;
+ APP_OP_MANAGE_ONGOING_CALLS = 103;
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 16a691c..9291a90 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -510,7 +510,7 @@
optional IntentFirewall intent_firewall = 65;
reserved 66; // job_scheduler_constants
- optional SettingProto job_scheduler_quota_controller_constants = 149 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ reserved 149; // job_scheduler_quota_controller_constants
reserved 150; // job_scheduler_time_controller_constants
optional SettingProto keep_profile_in_background = 67 [ (android.privacy).dest = DEST_AUTOMATIC ];
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 83c5391..d934b82 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -219,8 +219,10 @@
optional SettingProto emergency_assistance_application = 22 [ (android.privacy).dest = DEST_AUTOMATIC ];
message EmergencyResponse {
- optional SettingProto panic_gesture_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto panic_sound_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto emergency_gesture_enabled = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto emergency_gesture_sound_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ reserved 1,2;
}
optional EmergencyResponse emergency_response = 83;
diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto
index a264f18..a49a1ad 100644
--- a/core/proto/android/server/fingerprint.proto
+++ b/core/proto/android/server/fingerprint.proto
@@ -66,3 +66,36 @@
// Total number of permanent lockouts.
optional int32 permanent_lockout = 5;
}
+
+// Internal FingerprintService states. The above messages (FingerprintServiceDumpProto, etc)
+// are used for legacy metrics and should not be modified.
+message FingerprintServiceStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ repeated SensorStateProto sensor_states = 1;
+}
+
+// State of a single sensor.
+message SensorStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Unique sensorId
+ optional int32 sensor_id = 1;
+
+ // State of the sensor's scheduler. True if currently handling an operation, false if idle.
+ optional bool is_busy = 2;
+
+ // User states for this sensor.
+ repeated UserStateProto user_states = 3;
+}
+
+// State of a specific user for a specific sensor.
+message UserStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Android user ID
+ optional int32 user_id = 1;
+
+ // Number of fingerprints enrolled
+ optional int32 num_enrolled = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/stats/tls/enums.proto b/core/proto/android/stats/tls/enums.proto
new file mode 100644
index 0000000..0ae87ee
--- /dev/null
+++ b/core/proto/android/stats/tls/enums.proto
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+syntax = "proto2";
+package android.stats.tls;
+
+// Keep in sync with
+// external/conscrypt/{android,platform}/src/main/java/org/conscrypt/Platform.java
+enum Protocol {
+ UNKNOWN_PROTO = 0;
+ SSLv3 = 1;
+ TLSv1 = 2;
+ TLSv1_1 = 3;
+ TLSv1_2 = 4;
+ TLSv1_3 = 5;
+}
+
+// Cipher suites' ids are based on IANA's database:
+// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
+//
+// If you add new cipher suite, make sure id is the same as in IANA's database (see link above)
+//
+// Keep in sync with
+// external/conscrypt/{android,platform}/src/main/java/org/conscrypt/Platform.java
+enum CipherSuite {
+ UNKNOWN_CIPHER_SUITE = 0x0000;
+
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A;
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014;
+ TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035;
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009;
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013;
+ TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F;
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A;
+
+ // TLSv1.2 cipher suites
+ TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C;
+ TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D;
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F;
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030;
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B;
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C;
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9;
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8;
+
+ // Pre-Shared Key (PSK) cipher suites
+ TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C;
+ TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D;
+ TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = 0xC035;
+ TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = 0xC036;
+ TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAC;
+
+ // TLS 1.3 cipher suites
+ TLS_AES_128_GCM_SHA256 = 0x1301;
+ TLS_AES_256_GCM_SHA384 = 0x1302;
+ TLS_CHACHA20_POLY1305_SHA256 = 0x1303;
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0195451..95a7414 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -349,6 +349,7 @@
<protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
<protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
<protected-broadcast android:name="com.android.server.WifiManager.action.DEVICE_IDLE" />
+ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_DISPATCH" />
<protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" />
<protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" />
<protected-broadcast android:name="com.android.internal.action.EUICC_FACTORY_RESET" />
@@ -668,6 +669,10 @@
<!-- For tether entitlement recheck-->
<protected-broadcast
android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
+
+ <!-- Made protected in S (was added in R) -->
+ <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
@@ -2229,6 +2234,11 @@
<permission android:name="android.permission.BIND_INCALL_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Allows to query ongoing call details and manage ongoing calls
+ <p>Protection level: signature|appop -->
+ <permission android:name="android.permission.MANAGE_ONGOING_CALLS"
+ android:protectionLevel="signature|appop" />
+
<!-- Allows the app to request network scans from telephony.
<p>Not for use by third-party applications.
@SystemApi @hide-->
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 88493c9..d11875e 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -38,81 +38,7 @@
android:layout_gravity="center"
/>
</FrameLayout>
- <TextView
- android:id="@+id/app_name_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_app_name_margin_start"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:visibility="?attr/notificationHeaderAppNameVisibility"
- android:singleLine="true"
- />
- <TextView
- android:id="@+id/header_text_secondary_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:visibility="gone"/>
- <TextView
- android:id="@+id/header_text_secondary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:visibility="gone"
- android:singleLine="true"/>
- <TextView
- android:id="@+id/header_text_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:visibility="gone"/>
- <TextView
- android:id="@+id/header_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:visibility="gone"
- android:singleLine="true"/>
- <TextView
- android:id="@+id/time_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"/>
- <DateTimeView
- android:id="@+id/time"
- android:textAppearance="@style/TextAppearance.Material.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone" />
- <ViewStub
- android:id="@+id/chronometer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:layout="@layout/notification_template_part_chronometer"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_template_top_line" />
<com.android.internal.widget.NotificationExpandButton
android:id="@+id/expand_button"
android:background="@null"
@@ -123,42 +49,5 @@
android:visibility="gone"
android:contentDescription="@string/expand_button_content_description_collapsed"
/>
- <ImageView
- android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_alerted_size"
- android:layout_height="@dimen/notification_alerted_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="1dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_alerted_content_description"
- android:src="@drawable/ic_notifications_alerted"
- />
- <ImageButton
- android:id="@+id/feedback"
- android:layout_width="@dimen/notification_feedback_size"
- android:layout_height="@dimen/notification_feedback_size"
- android:layout_marginStart="6dp"
- android:layout_marginEnd="6dp"
- android:paddingTop="2dp"
- android:layout_gravity="center"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_feedback_indicator"
- android:background="?android:selectableItemBackgroundBorderless"
- android:visibility="gone"
- android:contentDescription="@string/notification_feedback_indicator"
- />
- <ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="1dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_top_line.xml b/core/res/res/layout/notification_template_top_line.xml
new file mode 100644
index 0000000..27fab85
--- /dev/null
+++ b/core/res/res/layout/notification_template_top_line.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<!-- extends ViewGroup -->
+<NotificationTopLineView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ >
+ <TextView
+ android:id="@+id/app_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_app_name_margin_start"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="?attr/notificationHeaderAppNameVisibility"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/header_text_secondary_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_text_secondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="gone"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/header_text_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="gone"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"/>
+ <DateTimeView
+ android:id="@+id/time"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone" />
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@dimen/notification_alerted_size"
+ android:layout_height="@dimen/notification_alerted_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:src="@drawable/ic_notifications_alerted"
+ />
+ <ImageButton
+ android:id="@+id/feedback"
+ android:layout_width="@dimen/notification_feedback_size"
+ android:layout_height="@dimen/notification_feedback_size"
+ android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ android:paddingTop="2dp"
+ android:layout_gravity="center"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_feedback_indicator"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_feedback_indicator"
+ />
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
+ />
+</NotificationTopLineView>
+
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index ba21082..70cfaa7 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1129,10 +1129,8 @@
<string name="capital_off" msgid="7443704171014626777">"ක්රියාවිරහිතයි"</string>
<string name="checked" msgid="9179896827054513119">"පරීක්ෂා කර ඇත"</string>
<string name="not_checked" msgid="7972320087569023342">"පරීක්ෂා කර නැත"</string>
- <!-- no translation found for selected (6614607926197755875) -->
- <skip />
- <!-- no translation found for not_selected (410652016565864475) -->
- <skip />
+ <string name="selected" msgid="6614607926197755875">"තෝරන ලදි"</string>
+ <string name="not_selected" msgid="410652016565864475">"තෝරා නොමැත"</string>
<string name="whichApplication" msgid="5432266899591255759">"පහත භාවිතයෙන් ක්රියාව සම්පූර්ණ කරන්න"</string>
<string name="whichApplicationNamed" msgid="6969946041713975681">"%1$s භාවිතා කරමින් ක්රියාව සම්පුර්ණ කරන්න"</string>
<string name="whichApplicationLabel" msgid="7852182961472531728">"ක්රියාව සම්පූර්ණ කරන්න"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index e3ddbd8..f8266ba 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3202,10 +3202,23 @@
<attr name="minHeight" />
<!-- Window layout affinity of this activity. Activities with the same window layout
- affinity will share the same layout record. If an activity is launched in freeform window,
- the activity will be launched to the latest position and size where any task, if the root
- activity of that task shares the same window layout affinity with the activity being
- launched. Window layout affinity is shared only among activities with the same UID.
+ affinity will share the same layout record. That is, if a user is opening an activity in
+ a new task on a display that can host freeform windows, and the user had opened a task
+ before and that task had a root activity who had the same window layout affinity, the
+ new task's window will be created in the same window mode and around the location which
+ the previously opened task was in.
+
+ <p>For example, if a user maximizes a task with root activity A and opens another
+ activity B that has the same window layout affinity as activity A has, activity B will
+ be created in fullscreen window mode. Similarly, if they move/resize a task with root
+ activity C and open another activity D that has the same window layout affinity as
+ activity C has, activity D will be in freeform window mode and as close to the position
+ of activity C as conditions permit. It doesn't require the user to keep the task with
+ activity A or activity C open. It won't, however, put any task into split-screen or PIP
+ window mode on launch.
+
+ <p>If the user is opening an activity with its window layout affinity for the first time,
+ the window mode and position is OEM defined.
<p>By default activity doesn't share any affinity with other activities. -->
<attr name="windowLayoutAffinity" format="string" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e6ebd4b..84e7d42 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -207,6 +207,9 @@
<!-- Default padding for dialogs. -->
<dimen name="dialog_padding">16dp</dimen>
+ <!-- The horizontal margin of the content in the notification shade -->
+ <dimen name="notification_shade_content_margin_horizontal">16dp</dimen>
+
<!-- The margin on the start of the content view -->
<dimen name="notification_content_margin_start">16dp</dimen>
@@ -857,4 +860,9 @@
<dimen name="waterfall_display_top_edge_size">0px</dimen>
<dimen name="waterfall_display_right_edge_size">0px</dimen>
<dimen name="waterfall_display_bottom_edge_size">0px</dimen>
+
+ <!-- The maximum height of a thumbnail in a ThumbnailTemplate. The image will be reduced to that height in case they are bigger. -->
+ <dimen name="controls_thumbnail_image_max_height">140dp</dimen>
+ <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
+ <dimen name="controls_thumbnail_image_max_width">280dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 94397d6..697f7e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2852,6 +2852,7 @@
<java-symbol type="id" name="header_text_secondary" />
<java-symbol type="id" name="expand_button" />
<java-symbol type="id" name="notification_header" />
+ <java-symbol type="id" name="notification_top_line" />
<java-symbol type="id" name="time_divider" />
<java-symbol type="id" name="header_text_divider" />
<java-symbol type="id" name="header_text_secondary_divider" />
@@ -2862,6 +2863,7 @@
<java-symbol type="drawable" name="ic_collapse_bundle" />
<java-symbol type="dimen" name="notification_min_content_height" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_shade_content_margin_horizontal" />
<java-symbol type="dimen" name="notification_content_margin_start" />
<java-symbol type="dimen" name="notification_content_margin_end" />
<java-symbol type="dimen" name="notification_reply_inset" />
@@ -4079,4 +4081,7 @@
<java-symbol type="array" name="config_keep_warming_services" />
<java-symbol type="string" name="config_display_features" />
<java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" />
+
+ <java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
+ <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
</resources>
diff --git a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
index f4ebe2f..63e4642 100644
--- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
+++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
@@ -32,6 +32,8 @@
import android.content.IIntentSender;
import android.content.Intent;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -39,11 +41,14 @@
import android.service.controls.actions.CommandAction;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.ControlActionWrapper;
+import android.service.controls.templates.ThumbnailTemplate;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -107,7 +112,8 @@
mPendingIntent = new PendingIntent(mIIntentSender);
- mControlsProviderService = new FakeControlsProviderService();
+ mControlsProviderService = new FakeControlsProviderService(
+ InstrumentationRegistry.getInstrumentation().getContext());
mControlsProvider = IControlsProvider.Stub.asInterface(
mControlsProviderService.onBind(intent));
}
@@ -134,7 +140,8 @@
verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
subscriptionCaptor.getValue().request(1000);
- verify(mSubscriber, times(2)).onNext(eq(mToken), controlCaptor.capture());
+ verify(mSubscriber, times(2))
+ .onNext(eq(mToken), controlCaptor.capture());
List<Control> values = controlCaptor.getAllValues();
assertTrue(equals(values.get(0), list.get(0)));
assertTrue(equals(values.get(1), list.get(1)));
@@ -210,26 +217,69 @@
.setStatus(Control.STATUS_OK)
.build();
- @SuppressWarnings("unchecked")
- ArgumentCaptor<Control> controlCaptor =
- ArgumentCaptor.forClass(Control.class);
- ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor =
- ArgumentCaptor.forClass(IControlsSubscription.Stub.class);
+ Control c = sendControlGetControl(control);
+ assertTrue(equals(c, control));
+ }
- ArrayList<Control> list = new ArrayList<>();
- list.add(control);
+ @Test
+ public void testThumbnailRescaled_bigger() throws RemoteException {
+ Context context = mControlsProviderService.getBaseContext();
+ int maxWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_width);
+ int maxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_height);
- mControlsProviderService.setControls(list);
+ int min = Math.min(maxWidth, maxHeight);
+ int max = Math.max(maxWidth, maxHeight);
- mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ Bitmap bitmap = Bitmap.createBitmap(max * 2, max * 2, Bitmap.Config.ALPHA_8);
+ Icon icon = Icon.createWithBitmap(bitmap);
+ ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, "");
- verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
- subscriptionCaptor.getValue().request(1);
+ Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
+ .setTitle("TEST_TITLE")
+ .setStatus(Control.STATUS_OK)
+ .setControlTemplate(template)
+ .build();
- verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture());
- Control c = controlCaptor.getValue();
- assertTrue(equals(c, list.get(0)));
+ Control c = sendControlGetControl(control);
+
+ ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate();
+ Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap();
+
+ // Aspect ratio is kept
+ assertEquals(sentBitmap.getWidth(), sentBitmap.getHeight());
+
+ assertEquals(min, sentBitmap.getWidth());
+ }
+
+ @Test
+ public void testThumbnailRescaled_smaller() throws RemoteException {
+ Context context = mControlsProviderService.getBaseContext();
+ int maxWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_width);
+ int maxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_height);
+
+ int min = Math.min(maxWidth, maxHeight);
+
+ Bitmap bitmap = Bitmap.createBitmap(min / 2, min / 2, Bitmap.Config.ALPHA_8);
+ Icon icon = Icon.createWithBitmap(bitmap);
+ ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, "");
+
+ Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
+ .setTitle("TEST_TITLE")
+ .setStatus(Control.STATUS_OK)
+ .setControlTemplate(template)
+ .build();
+
+ Control c = sendControlGetControl(control);
+
+ ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate();
+ Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap();
+
+ assertEquals(bitmap.getHeight(), sentBitmap.getHeight());
+ assertEquals(bitmap.getWidth(), sentBitmap.getWidth());
}
@Test
@@ -257,6 +307,32 @@
intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL)));
}
+ /**
+ * Sends the control through the publisher in {@code mControlsProviderService}, returning
+ * the control obtained by the subscriber
+ */
+ private Control sendControlGetControl(Control control) throws RemoteException {
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<Control> controlCaptor =
+ ArgumentCaptor.forClass(Control.class);
+ ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor =
+ ArgumentCaptor.forClass(IControlsSubscription.Stub.class);
+
+ ArrayList<Control> list = new ArrayList<>();
+ list.add(control);
+
+ mControlsProviderService.setControls(list);
+
+ mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
+ subscriptionCaptor.getValue().request(1);
+
+ verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture());
+ return controlCaptor.getValue();
+ }
+
private static boolean equals(Control c1, Control c2) {
if (c1 == c2) return true;
if (c1 == null || c2 == null) return false;
@@ -276,6 +352,11 @@
static class FakeControlsProviderService extends ControlsProviderService {
+ FakeControlsProviderService(Context context) {
+ super();
+ attachBaseContext(context);
+ }
+
private List<Control> mControls;
public void setControls(List<Control> controls) {
diff --git a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
index 87dc1b7..91a3ba7 100644
--- a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
+++ b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
@@ -103,6 +103,17 @@
}
@Test
+ public void testUnparcelingCorrectClass_thumbnail() {
+ ControlTemplate toParcel = new ThumbnailTemplate(TEST_ID, false, mIcon,
+ TEST_ACTION_DESCRIPTION);
+
+ ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+ assertEquals(ControlTemplate.TYPE_THUMBNAIL, fromParcel.getTemplateType());
+ assertTrue(fromParcel instanceof ThumbnailTemplate);
+ }
+
+ @Test
public void testUnparcelingCorrectClass_toggleRange() {
ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton,
new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"));
diff --git a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java
index 5112326..ef659af6 100644
--- a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java
@@ -66,8 +66,8 @@
* {@link android.widget.cts.TextViewOnReceiveContentCallbackTest}. This class tests some internal
* implementation details, e.g. fallback to the keyboard image API.
*/
-@RunWith(AndroidJUnit4.class)
@MediumTest
+@RunWith(AndroidJUnit4.class)
public class TextViewOnReceiveContentCallbackTest {
private static final Uri SAMPLE_CONTENT_URI = Uri.parse("content://com.example/path");
@@ -101,7 +101,7 @@
// Assert that the callback returns the MIME types declared in the EditorInfo in addition to
// the default.
- assertThat(mDefaultCallback.getSupportedMimeTypes(mEditText)).containsExactly(
+ assertThat(mDefaultCallback.getMimeTypes(mEditText)).containsExactly(
"text/*", "image/gif", "image/png");
}
@@ -118,7 +118,7 @@
onView(withId(mEditText.getId())).perform(clickOnTextAtIndex(0));
// Assert that the callback returns the default MIME types.
- assertThat(mDefaultCallback.getSupportedMimeTypes(mEditText)).containsExactly("text/*");
+ assertThat(mDefaultCallback.getMimeTypes(mEditText)).containsExactly("text/*");
}
@Test
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index a430dcd..e51bf5e4 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2923,6 +2923,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskDisplayArea.java"
},
+ "1396893178": {
+ "message": "createRootTask unknown displayId=%d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+ },
"1401295262": {
"message": "Mode default, asking user",
"level": "WARN",
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index f8db447..bee9f41 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -37,6 +37,18 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-1325223370": {
+ "message": "Task appeared taskId=%d listener=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
+ "-1312360667": {
+ "message": "createRootTask() displayId=%d winMode=%d listener=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
"-1006733970": {
"message": "Display added: %d",
"level": "VERBOSE",
@@ -55,6 +67,18 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-848099324": {
+ "message": "Letterbox Task Appeared: #%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
+ },
+ "-842742255": {
+ "message": "%s onTaskAppeared unknown taskId=%d winMode=%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
+ },
"-712674749": {
"message": "Clip description: %s",
"level": "VERBOSE",
@@ -67,11 +91,11 @@
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
},
- "-460572385": {
- "message": "Task appeared taskId=%d",
+ "-679492476": {
+ "message": "%s onTaskAppeared Primary taskId=%d",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
},
"-191422040": {
"message": "Transition animations finished, notifying core %s",
@@ -79,6 +103,12 @@
"group": "WM_SHELL_TRANSITIONS",
"at": "com\/android\/wm\/shell\/Transitions.java"
},
+ "154313206": {
+ "message": "%s onTaskAppeared Secondary taskId=%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
+ },
"157713005": {
"message": "Task info changed taskId=%d",
"level": "VERBOSE",
@@ -115,18 +145,36 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "1104702476": {
+ "message": "Letterbox Task Changed: #%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
+ },
"1184615936": {
"message": "Set drop target window visibility: displayId=%d visibility=%d",
"level": "VERBOSE",
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
},
+ "1218010718": {
+ "message": "Letterbox Task Vanished: #%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
+ },
"1481772149": {
"message": "Current target: %s",
"level": "VERBOSE",
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
},
+ "1842752748": {
+ "message": "Clip description: handlingDrag=%b mimeTypes=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
"1862198614": {
"message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
"level": "VERBOSE",
@@ -144,6 +192,12 @@
"level": "VERBOSE",
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
+ "2135461748": {
+ "message": "%s onTaskAppeared Supported",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
}
},
"groups": {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 5bd693a..fc0a76e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -20,9 +20,7 @@
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
import android.app.ActivityManager;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.SurfaceControl;
@@ -39,7 +37,7 @@
private final SyncTransactionQueue mSyncQueue;
- private final ArrayMap<Integer, SurfaceControl> mTasks = new ArrayMap<>();
+ private final ArraySet<Integer> mTasks = new ArraySet<>();
FullscreenTaskListener(SyncTransactionQueue syncQueue) {
mSyncQueue = syncQueue;
@@ -48,17 +46,17 @@
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mTasks) {
- if (mTasks.containsKey(taskInfo.taskId)) {
+ if (mTasks.contains(taskInfo.taskId)) {
throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
taskInfo.taskId);
- mTasks.put(taskInfo.taskId, leash);
+ mTasks.add(taskInfo.taskId);
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
- updateSurfacePosition(t, taskInfo, leash);
t.setWindowCrop(leash, null);
+ t.setPosition(leash, 0, 0);
// TODO(shell-transitions): Eventually set everything in transition so there's no
// SF Transaction here.
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -73,7 +71,7 @@
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
synchronized (mTasks) {
- if (mTasks.remove(taskInfo.taskId) == null) {
+ if (!mTasks.remove(taskInfo.taskId)) {
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
return;
}
@@ -83,23 +81,6 @@
}
@Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- synchronized (mTasks) {
- if (!mTasks.containsKey(taskInfo.taskId)) {
- Slog.e(TAG, "Changed Task wasn't appeared or already vanished: #"
- + taskInfo.taskId);
- return;
- }
- final SurfaceControl leash = mTasks.get(taskInfo.taskId);
- mSyncQueue.runInSync(t -> {
- // Reposition the task in case the bounds has been changed (such as Task level
- // letterboxing).
- updateSurfacePosition(t, taskInfo, leash);
- });
- }
- }
-
- @Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
@@ -112,12 +93,4 @@
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
- /** Places the Task surface to the latest position. */
- private static void updateSurfacePosition(SurfaceControl.Transaction t,
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- // TODO(170725334) drop this after ag/12876439
- final Configuration config = taskInfo.getConfiguration();
- final Rect bounds = config.windowConfiguration.getBounds();
- t.setPosition(leash, bounds.left, bounds.top);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java
new file mode 100644
index 0000000..9010c20
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when
+ * it's presented in the letterbox mode either because orientations of a top activity and a device
+ * don't match or because a top activity is in a size compat mode.
+ */
+final class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = "LetterboxTaskListener";
+
+ private final SyncTransactionQueue mSyncQueue;
+
+ private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
+
+ LetterboxTaskListener(SyncTransactionQueue syncQueue) {
+ mSyncQueue = syncQueue;
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ synchronized (mLeashByTaskId) {
+ if (mLeashByTaskId.get(taskInfo.taskId) != null) {
+ throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d",
+ taskInfo.taskId);
+ mLeashByTaskId.put(taskInfo.taskId, leash);
+ final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
+ final Rect activtyBounds = taskInfo.letterboxActivityBounds;
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ setPositionAndWindowCrop(
+ t, leash, activtyBounds, taskBounds, taskPositionInParent);
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ synchronized (mLeashByTaskId) {
+ if (mLeashByTaskId.get(taskInfo.taskId) == null) {
+ Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+ return;
+ }
+ mLeashByTaskId.remove(taskInfo.taskId);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d",
+ taskInfo.taskId);
+ }
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ synchronized (mLeashByTaskId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d",
+ taskInfo.taskId);
+ final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
+ final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
+ final Rect activtyBounds = taskInfo.letterboxActivityBounds;
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ setPositionAndWindowCrop(
+ t, leash, activtyBounds, taskBounds, taskPositionInParent);
+ });
+ }
+ }
+
+ private static void setPositionAndWindowCrop(
+ SurfaceControl.Transaction transaction,
+ SurfaceControl leash,
+ final Rect activityBounds,
+ final Rect taskBounds,
+ final Point taskPositionInParent) {
+ Rect activtyInTaskCoordinates = new Rect(activityBounds);
+ activtyInTaskCoordinates.offset(-taskBounds.left, -taskBounds.top);
+ transaction.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ transaction.setWindowCrop(leash, activtyInTaskCoordinates);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index cbc1c8d..d4ff275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -20,8 +20,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
@@ -29,6 +27,7 @@
import android.annotation.IntDef;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
+import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.Log;
@@ -45,7 +44,6 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -64,14 +62,14 @@
public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
public static final int TASK_LISTENER_TYPE_PIP = -4;
- public static final int TASK_LISTENER_TYPE_SPLIT_SCREEN = -5;
+ public static final int TASK_LISTENER_TYPE_LETTERBOX = -5;
@IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
TASK_LISTENER_TYPE_UNDEFINED,
TASK_LISTENER_TYPE_FULLSCREEN,
TASK_LISTENER_TYPE_MULTI_WINDOW,
TASK_LISTENER_TYPE_PIP,
- TASK_LISTENER_TYPE_SPLIT_SCREEN,
+ TASK_LISTENER_TYPE_LETTERBOX,
})
public @interface TaskListenerType {}
@@ -118,6 +116,7 @@
ShellExecutor mainExecutor, ShellExecutor animExecutor) {
super(taskOrganizerController, mainExecutor);
addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN);
+ addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX);
mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
}
@@ -137,6 +136,14 @@
}
}
+ public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s",
+ displayId, windowingMode, listener.toString());
+ final IBinder cookie = new Binder();
+ setPendingLaunchCookieListener(cookie, listener);
+ super.createRootTask(displayId, windowingMode, cookie);
+ }
+
/**
* Adds a listener for a specific task id.
*/
@@ -236,10 +243,10 @@
private void onTaskAppeared(TaskAppearedInfo info) {
final int taskId = info.getTaskInfo().taskId;
- ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d", taskId);
mTasks.put(taskId, info);
final TaskListener listener =
getTaskListener(info.getTaskInfo(), true /*removeLaunchCookieIfNeeded*/);
+ ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d listener=%s", taskId, listener);
if (listener != null) {
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
@@ -329,26 +336,25 @@
if (listener != null) return listener;
// Next we try type specific listeners.
- final int windowingMode = getWindowingMode(runningTaskInfo);
- final int taskListenerType = windowingModeToTaskListenerType(windowingMode);
+ final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo);
return mTaskListeners.get(taskListenerType);
}
@WindowingMode
- private static int getWindowingMode(RunningTaskInfo taskInfo) {
+ public static int getWindowingMode(RunningTaskInfo taskInfo) {
return taskInfo.configuration.windowConfiguration.getWindowingMode();
}
- private static @TaskListenerType int windowingModeToTaskListenerType(
- @WindowingMode int windowingMode) {
+ @VisibleForTesting
+ static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) {
+ final int windowingMode = getWindowingMode(runningTaskInfo);
switch (windowingMode) {
case WINDOWING_MODE_FULLSCREEN:
- return TASK_LISTENER_TYPE_FULLSCREEN;
+ return runningTaskInfo.letterboxActivityBounds != null
+ ? TASK_LISTENER_TYPE_LETTERBOX
+ : TASK_LISTENER_TYPE_FULLSCREEN;
case WINDOWING_MODE_MULTI_WINDOW:
return TASK_LISTENER_TYPE_MULTI_WINDOW;
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
- case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
- return TASK_LISTENER_TYPE_SPLIT_SCREEN;
case WINDOWING_MODE_PINNED:
return TASK_LISTENER_TYPE_PIP;
case WINDOWING_MODE_FREEFORM:
@@ -362,10 +368,10 @@
switch (type) {
case TASK_LISTENER_TYPE_FULLSCREEN:
return "TASK_LISTENER_TYPE_FULLSCREEN";
+ case TASK_LISTENER_TYPE_LETTERBOX:
+ return "TASK_LISTENER_TYPE_LETTERBOX";
case TASK_LISTENER_TYPE_MULTI_WINDOW:
return "TASK_LISTENER_TYPE_MULTI_WINDOW";
- case TASK_LISTENER_TYPE_SPLIT_SCREEN:
- return "TASK_LISTENER_TYPE_SPLIT_SCREEN";
case TASK_LISTENER_TYPE_PIP:
return "TASK_LISTENER_TYPE_PIP";
case TASK_LISTENER_TYPE_UNDEFINED:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 8f8b98b..bf5b1d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -16,8 +16,17 @@
package com.android.wm.shell.draganddrop;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
+import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_SHORTCUT_ID;
+import static android.content.Intent.EXTRA_TASK_ID;
+import static android.content.Intent.EXTRA_USER;
import static android.view.DragEvent.ACTION_DRAG_ENDED;
import static android.view.DragEvent.ACTION_DRAG_ENTERED;
import static android.view.DragEvent.ACTION_DRAG_EXITED;
@@ -33,15 +42,24 @@
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DragEvent;
@@ -68,6 +86,7 @@
private static final String TAG = DragAndDropController.class.getSimpleName();
+ private final Context mContext;
private final DisplayController mDisplayController;
private SplitScreen mSplitScreen;
@@ -76,7 +95,8 @@
private DragLayout mDragLayout;
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
- public DragAndDropController(DisplayController displayController) {
+ public DragAndDropController(Context context, DisplayController displayController) {
+ mContext = context;
mDisplayController = displayController;
mDisplayController.addDisplayWindowListener(this);
}
@@ -135,13 +155,16 @@
event.getOffsetX(), event.getOffsetY());
final int displayId = target.getDisplay().getDisplayId();
final PerDisplay pd = mDisplayDropTargets.get(displayId);
+ final ClipDescription description = event.getClipDescription();
if (event.getAction() == ACTION_DRAG_STARTED) {
- final ClipDescription description = event.getClipDescription();
- final boolean hasValidClipData = description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY);
+ final boolean hasValidClipData = description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
+ || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
+ || description.hasMimeType(MIMETYPE_APPLICATION_TASK);
mIsHandlingDrag = hasValidClipData;
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Clip description: %s",
- getMimeTypes(description));
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Clip description: handlingDrag=%b mimeTypes=%s",
+ mIsHandlingDrag, getMimeTypes(description));
}
if (!mIsHandlingDrag) {
@@ -163,31 +186,7 @@
mDragLayout.update(event);
break;
case ACTION_DROP: {
- final SurfaceControl dragSurface = event.getDragSurface();
- final View dragLayout = mDragLayout;
- final ClipData data = event.getClipData();
- return mDragLayout.drop(event, dragSurface, (dropTargetBounds) -> {
- if (dropTargetBounds != null) {
- // TODO(b/169894807): Properly handle the drop, for now just launch it
- if (data.getItemCount() > 0) {
- Intent intent = data.getItemAt(0).getIntent();
- PendingIntent pi = intent.getParcelableExtra(
- ClipDescription.EXTRA_PENDING_INTENT);
- try {
- pi.send();
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "Failed to launch activity", e);
- }
- }
- }
-
- setDropTargetWindowVisibility(pd, View.INVISIBLE);
- pd.dropTarget.removeView(dragLayout);
-
- // Clean up the drag surface
- mTransaction.reparent(dragSurface, null);
- mTransaction.apply();
- });
+ return handleDrop(event, pd);
}
case ACTION_DRAG_EXITED: {
// Either one of DROP or EXITED will happen, and when EXITED we won't consume
@@ -211,6 +210,62 @@
return true;
}
+ /**
+ * Handles dropping on the drop target.
+ */
+ private boolean handleDrop(DragEvent event, PerDisplay pd) {
+ final ClipData data = event.getClipData();
+ final ClipDescription description = event.getClipDescription();
+ final SurfaceControl dragSurface = event.getDragSurface();
+ final View dragLayout = mDragLayout;
+ final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+ final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+ return mDragLayout.drop(event, dragSurface, (dropTargetBounds) -> {
+ if (dropTargetBounds != null && data.getItemCount() > 0) {
+ final Intent intent = data.getItemAt(0).getIntent();
+ // TODO(b/169894807): Properly handle the drop, for now just launch it
+ if (isTask) {
+ int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(
+ taskId, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ } else if (isShortcut) {
+ try {
+ Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
+ ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
+ : null;
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(
+ intent.getStringExtra(EXTRA_PACKAGE_NAME),
+ intent.getStringExtra(EXTRA_SHORTCUT_ID),
+ null /* sourceBounds */, opts,
+ intent.getParcelableExtra(EXTRA_USER));
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ } else {
+ PendingIntent pi = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ try {
+ pi.send();
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Failed to launch activity", e);
+ }
+ }
+ }
+
+ setDropTargetWindowVisibility(pd, View.INVISIBLE);
+ pd.dropTarget.removeView(dragLayout);
+
+ // Clean up the drag surface
+ mTransaction.reparent(dragSurface, null);
+ mTransaction.apply();
+ });
+ }
+
private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Set drop target window visibility: displayId=%d visibility=%d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 3ded409..59c79ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -22,7 +22,6 @@
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import android.media.session.MediaController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.pip.tv.PipController;
@@ -35,13 +34,6 @@
*/
public interface Pip {
/**
- * Registers {@link com.android.wm.shell.pip.tv.PipController.Listener} that gets called.
- * whenever receiving notification on changes in PIP.
- */
- default void addListener(PipController.Listener listener) {
- }
-
- /**
* Registers a {@link PipController.MediaListener} to PipController.
*/
default void addMediaListener(PipController.MediaListener listener) {
@@ -68,17 +60,8 @@
}
/**
- * Get current play back state. (e.g: Used in TV)
- *
- * @return The state of defined in PipController.
- */
- default int getPlaybackState() {
- return -1;
- }
-
- /**
* Get the touch handler which manages all the touch handling for PIP on the Phone,
- * including moving, dismissing and expanding the PIP. (Do not used in TV)
+ * including moving, dismissing and expanding the PIP. (Do not use in TV)
*
* @return
*/
@@ -87,15 +70,6 @@
}
/**
- * Get MediaController.
- *
- * @return The MediaController instance.
- */
- default MediaController getMediaController() {
- return null;
- }
-
- /**
* Hides the PIP menu.
*/
default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
@@ -171,12 +145,6 @@
}
/**
- * Removes a {@link PipController.Listener} from PipController.
- */
- default void removeListener(PipController.Listener listener) {
- }
-
- /**
* Removes a {@link PipController.MediaListener} from PipController.
*/
default void removeMediaListener(PipController.MediaListener listener) {
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 9b4524a..a05aac9 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
@@ -772,6 +772,10 @@
if (animator == null || !animator.isRunning()
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
if (mState.isInPip() && fromRotation) {
+ // Update bounds state to final destination first. It's important to do this
+ // before finishing & cancelling the transition animation so that the MotionHelper
+ // bounds are synchronized to the destination bounds when the animation ends.
+ mPipBoundsState.setBounds(destinationBoundsOut);
// If we are rotating while there is a current animation, immediately cancel the
// animation (remove the listeners so we don't trigger the normal finish resize
// call that should only happen on the update thread)
@@ -785,7 +789,6 @@
sendOnPipTransitionCancelled(direction);
sendOnPipTransitionFinished(direction);
}
- mPipBoundsState.setBounds(destinationBoundsOut);
// Create a reset surface transaction for the new bounds and update the window
// container transaction
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
index b27af65..b9422ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
@@ -545,14 +545,14 @@
/**
* Adds a {@link Listener} to PipController.
*/
- public void addListener(Listener listener) {
+ void addListener(Listener listener) {
mListeners.add(listener);
}
/**
* Removes a {@link Listener} from PipController.
*/
- public void removeListener(Listener listener) {
+ void removeListener(Listener listener) {
mListeners.remove(listener);
}
@@ -641,7 +641,7 @@
/**
* Gets the {@link android.media.session.MediaController} for the PIPed activity.
*/
- public MediaController getMediaController() {
+ MediaController getMediaController() {
return mPipMediaController;
}
@@ -655,7 +655,7 @@
* This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
* or {@link #PLAYBACK_STATE_UNAVAILABLE}.
*/
- public int getPlaybackState() {
+ int getPlaybackState() {
if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
return PLAYBACK_STATE_UNAVAILABLE;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java
index ff617ed..eb82357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java
@@ -40,7 +40,7 @@
private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
- private final SplitScreenTaskOrganizer mSplits;
+ private final SplitScreenTaskListener mSplits;
private final TransactionPool mTransactionPool;
private final Handler mHandler;
private final TaskOrganizer mTaskOrganizer;
@@ -92,7 +92,7 @@
private boolean mPausedTargetAdjusted = false;
private boolean mAdjustedWhileHidden = false;
- DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler,
+ DividerImeController(SplitScreenTaskListener splits, TransactionPool pool, Handler handler,
TaskOrganizer taskOrganizer) {
mSplits = splits;
mTransactionPool = pool;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
index 2b14e8b..c6496ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
@@ -155,7 +155,7 @@
private boolean mAdjustedForIme;
private DividerState mState;
- private SplitScreenTaskOrganizer mTiles;
+ private SplitScreenTaskListener mTiles;
boolean mFirstLayout = true;
int mDividerPositionX;
int mDividerPositionY;
@@ -354,7 +354,7 @@
void injectDependencies(SplitScreenController splitScreenController,
DividerWindowManager windowManager, DividerState dividerState,
- DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl,
+ DividerCallbacks callback, SplitScreenTaskListener tiles, SplitDisplayLayout sdl,
DividerImeController imeController, WindowManagerProxy wmProxy) {
mSplitScreenController = splitScreenController;
mWindowManager = windowManager;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java
index 3c0f939..7d5e1a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java
@@ -47,7 +47,7 @@
private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
- SplitScreenTaskOrganizer mTiles;
+ SplitScreenTaskListener mTiles;
DisplayLayout mDisplayLayout;
Context mContext;
@@ -62,7 +62,7 @@
Rect mAdjustedPrimary = null;
Rect mAdjustedSecondary = null;
- public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskOrganizer taskTiles) {
+ public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskListener taskTiles) {
mTiles = taskTiles;
mDisplayLayout = dl;
mContext = ctx;
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 43e4d62..69d428a 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
@@ -75,7 +75,7 @@
private final DividerState mDividerState = new DividerState();
private final ForcedResizableInfoActivityController mForcedResizableController;
private final Handler mHandler;
- private final SplitScreenTaskOrganizer mSplits;
+ private final SplitScreenTaskListener mSplits;
private final SystemWindows mSystemWindows;
final TransactionPool mTransactionPool;
private final WindowManagerProxy mWindowManagerProxy;
@@ -117,7 +117,7 @@
mTransactionPool = transactionPool;
mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
mTaskOrganizer = shellTaskOrganizer;
- mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer);
+ mSplits = new SplitScreenTaskListener(this, shellTaskOrganizer);
mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler,
shellTaskOrganizer);
mRotationController =
@@ -164,6 +164,14 @@
// Don't initialize the divider or anything until we get the default display.
}
+ void onSplitScreenSupported() {
+ // Set starting tile bounds based on middle target
+ final WindowContainerTransaction tct = new WindowContainerTransaction();
+ int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+ mSplitLayout.resizeSplits(midPos, tct);
+ mTaskOrganizer.applyTransaction(tct);
+ }
+
@Override
public boolean isSplitScreenSupported() {
return mSplits.isSplitScreenSupported();
@@ -196,11 +204,6 @@
}
try {
mSplits.init();
- // Set starting tile bounds based on middle target
- final WindowContainerTransaction tct = new WindowContainerTransaction();
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- mSplitLayout.resizeSplits(midPos, tct);
- mTaskOrganizer.applyTransaction(tct);
} catch (Exception e) {
Slog.e(TAG, "Failed to register docked stack listener", e);
removeDivider();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
similarity index 83%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
index f763d6d..191a317 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
@@ -23,25 +23,24 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_SPLIT_SCREEN;
-import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+import static com.android.wm.shell.ShellTaskOrganizer.getWindowingMode;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager.RunningTaskInfo;
import android.graphics.Rect;
-import android.os.RemoteException;
import android.util.Log;
-import android.view.Display;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import java.io.PrintWriter;
-class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = "SplitScreenTaskOrg";
+class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = "SplitScreenTaskListener";
private static final boolean DEBUG = SplitScreenController.DEBUG;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -58,20 +57,19 @@
final SurfaceSession mSurfaceSession = new SurfaceSession();
- SplitScreenTaskOrganizer(SplitScreenController splitScreenController,
+ SplitScreenTaskListener(SplitScreenController splitScreenController,
ShellTaskOrganizer shellTaskOrganizer) {
mSplitScreenController = splitScreenController;
mTaskOrganizer = shellTaskOrganizer;
- mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_SPLIT_SCREEN);
}
- void init() throws RemoteException {
+ void init() {
synchronized (this) {
try {
- mPrimary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- mSecondary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mTaskOrganizer.createRootTask(
+ DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this);
+ mTaskOrganizer.createRootTask(
+ DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this);
} catch (Exception e) {
// teardown to prevent callbacks
mTaskOrganizer.removeListener(this);
@@ -95,19 +93,26 @@
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (this) {
- if (mPrimary == null || mSecondary == null) {
- Log.w(TAG, "Received onTaskAppeared before creating root tasks " + taskInfo);
- return;
- }
-
- if (taskInfo.token.equals(mPrimary.token)) {
+ final int winMode = getWindowingMode(taskInfo);
+ if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ ProtoLog.v(WM_SHELL_TASK_ORG,
+ "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId);
+ mPrimary = taskInfo;
mPrimarySurface = leash;
- } else if (taskInfo.token.equals(mSecondary.token)) {
+ } else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ ProtoLog.v(WM_SHELL_TASK_ORG,
+ "%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId);
+ mSecondary = taskInfo;
mSecondarySurface = leash;
+ } else {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d",
+ TAG, taskInfo.taskId, winMode);
}
if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
mSplitScreenSupported = true;
+ mSplitScreenController.onSplitScreenSupported();
+ ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG);
// Initialize dim surfaces:
mPrimaryDim = new SurfaceControl.Builder(mSurfaceSession)
@@ -240,10 +245,13 @@
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
+ pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported);
+ if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId);
+ if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId);
}
@Override
public String toString() {
- return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_SPLIT_SCREEN);
+ return TAG;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
index 47e7c99..c51bbeb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
@@ -88,7 +88,7 @@
mTaskOrganizer = taskOrganizer;
}
- void dismissOrMaximizeDocked(final SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
+ void dismissOrMaximizeDocked(final SplitScreenTaskListener tiles, SplitDisplayLayout layout,
final boolean dismissOrMaximize) {
mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize));
}
@@ -189,7 +189,7 @@
*
* @return whether the home stack is resizable
*/
- boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
+ boolean applyEnterSplit(SplitScreenTaskListener tiles, SplitDisplayLayout layout) {
// Set launchtile first so that any stack created after
// getAllRootTaskInfos and before reparent (even if unlikely) are placed
// correctly.
@@ -242,7 +242,7 @@
* split (thus resulting in the top of the secondary split becoming
* fullscreen. {@code false} resolves the other way.
*/
- void applyDismissSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
+ void applyDismissSplit(SplitScreenTaskListener tiles, SplitDisplayLayout layout,
boolean dismissOrMaximize) {
// Set launch root first so that any task created after getChildContainers and
// before reparent (pretty unlikely) are put into fullscreen.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java
new file mode 100644
index 0000000..45d4d5d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxTaskListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LetterboxTaskListenerTest {
+
+ private static final Rect ACTIVITY_BOUNDS = new Rect(300, 200, 700, 400);
+ private static final Rect TASK_BOUNDS = new Rect(200, 100, 800, 500);
+ private static final Rect TASK_BOUNDS_2 = new Rect(300, 200, 800, 500);
+ private static final Point TASK_POSITION_IN_PARENT = new Point(100, 50);
+ private static final Point TASK_POSITION_IN_PARENT_2 = new Point(200, 100);
+
+ private static final Rect EXPECTED_WINDOW_CROP = new Rect(100, 100, 500, 300);
+ private static final Rect EXPECTED_WINDOW_CROP_2 = new Rect(0, 0, 400, 200);
+
+ private static final RunningTaskInfo TASK_INFO = createTaskInfo(
+ /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS, TASK_POSITION_IN_PARENT);
+
+ private static final RunningTaskInfo TASK_INFO_2 = createTaskInfo(
+ /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS_2, TASK_POSITION_IN_PARENT_2);
+
+ @Mock private SurfaceControl mLeash;
+ @Mock private SurfaceControl.Transaction mTransaction;
+ private LetterboxTaskListener mLetterboxTaskListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLetterboxTaskListener = new LetterboxTaskListener(
+ new SyncTransactionQueue(
+ new TransactionPool() {
+ @Override
+ public SurfaceControl.Transaction acquire() {
+ return mTransaction;
+ }
+
+ @Override
+ public void release(SurfaceControl.Transaction t) {
+ }
+ },
+ new Handler(Looper.getMainLooper())));
+ }
+
+ @Test
+ public void testOnTaskAppearedAndonTaskInfoChanged_setCorrectPositionAndCrop() {
+ mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
+
+ verify(mTransaction).setPosition(
+ eq(mLeash),
+ eq((float) TASK_POSITION_IN_PARENT.x),
+ eq((float) TASK_POSITION_IN_PARENT.y));
+ // Should return activty coordinates offset by task coordinates
+ verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP));
+
+ mLetterboxTaskListener.onTaskInfoChanged(TASK_INFO_2);
+
+ verify(mTransaction).setPosition(
+ eq(mLeash),
+ eq((float) TASK_POSITION_IN_PARENT_2.x),
+ eq((float) TASK_POSITION_IN_PARENT_2.y));
+ // Should return activty coordinates offset by task coordinates
+ verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP_2));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
+ mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
+ mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
+ }
+
+ private static RunningTaskInfo createTaskInfo(
+ int taskId,
+ final Rect activityBounds,
+ final Rect taskBounds,
+ final Point taskPositionInParent) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
+ taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
+ taskInfo.positionInParent = new Point(taskPositionInParent);
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 07a6bda..35a2293c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,15 +16,18 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERBOX;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -34,6 +37,7 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.pm.ParceledListSlice;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -42,6 +46,7 @@
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -242,10 +247,41 @@
assertTrue(gotException);
}
- private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ @Test
+ public void testTaskInfoToTaskListenerType_whenLetterboxBoundsPassed_returnsLetterboxType() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId */ 1,
+ WINDOWING_MODE_FULLSCREEN,
+ /* letterboxActivityBounds */ new Rect(1, 1, 1, 1));
+
+ assertEquals(
+ ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo),
+ TASK_LISTENER_TYPE_LETTERBOX);
+ }
+
+ @Test
+ public void testTaskInfoToTaskListenerType_whenLetterboxBoundsIsNull_returnsFullscreenType() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId */ 1, WINDOWING_MODE_FULLSCREEN, /* letterboxActivityBounds */ null);
+
+ assertEquals(
+ ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo),
+ TASK_LISTENER_TYPE_FULLSCREEN);
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
+
+ private static RunningTaskInfo createTaskInfo(
+ int taskId, int windowingMode, @Nullable Rect letterboxActivityBounds) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ taskInfo.letterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds);
+ return taskInfo;
+ }
}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 42cf53b..3905e0b 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -52,9 +52,11 @@
void registerLocationListener(String provider, in LocationRequest request, in ILocationListener listener, String packageName, String attributionTag, String listenerId);
void unregisterLocationListener(in ILocationListener listener);
- void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent intent, String packageName, String attributionTag);
+ void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent pendingIntent, String packageName, String attributionTag);
void unregisterLocationPendingIntent(in PendingIntent intent);
+ void injectLocation(in Location location);
+
void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag);
void removeGeofence(in PendingIntent intent);
@@ -89,7 +91,6 @@
void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String attributionTag);
void flushGnssBatch();
void stopGnssBatch();
- void injectLocation(in Location location);
List<String> getAllProviders();
List<String> getProviders(in Criteria criteria, boolean enabledOnly);
diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java
index 2738ff4f..0ff0a72 100644
--- a/location/java/android/location/LocationListener.java
+++ b/location/java/android/location/LocationListener.java
@@ -19,12 +19,11 @@
import android.annotation.NonNull;
import android.os.Bundle;
+import java.util.concurrent.Executor;
+
/**
- * Used for receiving notifications from the LocationManager when
- * the location has changed. These methods are called if the
- * LocationListener has been registered with the location manager service
- * using the {@link LocationManager#requestLocationUpdates(String, long, float, LocationListener)}
- * method.
+ * Used for receiving notifications when the device location has changed. These methods are called
+ * when the listener has been registered with the LocationManager.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
@@ -32,6 +31,8 @@
* <a href="{@docRoot}guide/topics/location/obtaining-user-location.html">Obtaining User
* Location</a> developer guide.</p>
* </div>
+ *
+ * @see LocationManager#requestLocationUpdates(String, LocationRequest, Executor, LocationListener)
*/
public interface LocationListener {
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index ac775ca..3493693 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -216,15 +216,15 @@
* Key used for an extra holding a boolean enabled/disabled status value when a provider
* enabled/disabled event is broadcast using a PendingIntent.
*
- * @see #requestLocationUpdates(String, long, float, PendingIntent)
+ * @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
*/
public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
/**
- * Key used for an extra holding a {@link Location} value when a location change is broadcast
- * using a PendingIntent.
+ * Key used for an extra holding a {@link Location} value when a location change is sent using
+ * a PendingIntent.
*
- * @see #requestLocationUpdates(String, long, float, PendingIntent)
+ * @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
*/
public static final String KEY_LOCATION_CHANGED = "location";
@@ -1322,27 +1322,26 @@
Preconditions.checkArgument(provider != null, "invalid null provider");
Preconditions.checkArgument(locationRequest != null, "invalid null location request");
- synchronized (sLocationListeners) {
- WeakReference<LocationListenerTransport> reference = sLocationListeners.get(listener);
- LocationListenerTransport transport = reference != null ? reference.get() : null;
- if (transport == null) {
- transport = new LocationListenerTransport(listener, executor);
- sLocationListeners.put(listener, new WeakReference<>(transport));
- } else {
- transport.setExecutor(executor);
- }
+ try {
+ synchronized (sLocationListeners) {
+ WeakReference<LocationListenerTransport> reference = sLocationListeners.get(
+ listener);
+ LocationListenerTransport transport = reference != null ? reference.get() : null;
+ if (transport == null) {
+ transport = new LocationListenerTransport(listener, executor);
+ } else {
+ Preconditions.checkState(transport.isRegistered());
+ transport.setExecutor(executor);
+ }
- try {
- // making the service call while under lock is less than ideal since LMS must
- // make sure that callbacks are not made on the same thread - however it is the
- // easiest way to guarantee that clients will not receive callbacks after
- // unregistration is complete.
mService.registerLocationListener(provider, locationRequest, transport,
mContext.getPackageName(), mContext.getAttributionTag(),
AppOpsManager.toReceiverId(listener));
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+
+ sLocationListeners.put(listener, new WeakReference<>(transport));
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -1429,23 +1428,17 @@
public void removeUpdates(@NonNull LocationListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener");
- synchronized (sLocationListeners) {
- WeakReference<LocationListenerTransport> reference = sLocationListeners.remove(
- listener);
- LocationListenerTransport transport = reference != null ? reference.get() : null;
- if (transport != null) {
- transport.unregister();
-
- try {
- // making the service call while under lock is less than ideal since LMS must
- // make sure that callbacks are not made on the same thread - however it is the
- // easiest way to guarantee that clients will not receive callbacks after
- // unregistration is complete.
+ try {
+ synchronized (sLocationListeners) {
+ WeakReference<LocationListenerTransport> ref = sLocationListeners.remove(listener);
+ LocationListenerTransport transport = ref != null ? ref.get() : null;
+ if (transport != null) {
+ transport.unregister();
mService.unregisterLocationListener(transport);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
}
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -2568,7 +2561,7 @@
@Nullable private volatile LocationListener mListener;
LocationListenerTransport(LocationListener listener, Executor executor) {
- Preconditions.checkArgument(listener != null, "invalid null listener/callback");
+ Preconditions.checkArgument(listener != null, "invalid null listener");
mListener = listener;
setExecutor(executor);
}
@@ -2578,6 +2571,10 @@
mExecutor = executor;
}
+ boolean isRegistered() {
+ return mListener != null;
+ }
+
void unregister() {
mListener = null;
}
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7a3a4b2..32ac374b 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -171,7 +171,9 @@
if (manager != null) {
try {
manager.onSetAllowed(mAllowed);
- } catch (RemoteException | RuntimeException e) {
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@@ -191,7 +193,9 @@
if (manager != null) {
try {
manager.onSetProperties(mProperties);
- } catch (RemoteException | RuntimeException e) {
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@@ -248,7 +252,9 @@
try {
manager.onReportLocation(location);
- } catch (RemoteException | RuntimeException e) {
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@@ -339,6 +345,8 @@
manager.onSetProperties(mProperties);
manager.onSetAllowed(mAllowed);
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 98ca2f9..4b208ce 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -362,28 +362,34 @@
@Override
public void onEvent(int event, int arg, @Nullable ArrayList<Byte> data)
throws RemoteException {
- mEventHandler.sendMessage(mEventHandler.obtainMessage(
+ if (mEventHandler != null) {
+ mEventHandler.sendMessage(mEventHandler.obtainMessage(
EventHandler.MSG_CAS_EVENT, event, arg, data));
+ }
}
@Override
public void onSessionEvent(@NonNull ArrayList<Byte> sessionId,
int event, int arg, @Nullable ArrayList<Byte> data)
throws RemoteException {
- Message msg = mEventHandler.obtainMessage();
- msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
- msg.arg1 = event;
- msg.arg2 = arg;
- Bundle bundle = new Bundle();
- bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId));
- bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data));
- msg.setData(bundle);
- mEventHandler.sendMessage(msg);
+ if (mEventHandler != null) {
+ Message msg = mEventHandler.obtainMessage();
+ msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
+ msg.arg1 = event;
+ msg.arg2 = arg;
+ Bundle bundle = new Bundle();
+ bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId));
+ bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data));
+ msg.setData(bundle);
+ mEventHandler.sendMessage(msg);
+ }
}
@Override
public void onStatusUpdate(byte status, int arg)
throws RemoteException {
- mEventHandler.sendMessage(mEventHandler.obtainMessage(
+ if (mEventHandler != null) {
+ mEventHandler.sendMessage(mEventHandler.obtainMessage(
EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
+ }
}
};
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index a98ea69..2d25061 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -97,6 +97,7 @@
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
+ field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
@@ -17846,6 +17847,7 @@
public class CaptureResult extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureResult.Key<?>> {
method @Nullable public <T> T get(android.hardware.camera2.CaptureResult.Key<T>);
+ method @NonNull public String getCameraId();
method public long getFrameNumber();
method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getKeys();
method @NonNull public android.hardware.camera2.CaptureRequest getRequest();
@@ -37407,6 +37409,9 @@
ctor public CallLog.Calls();
method public static String getLastOutgoingCall(android.content.Context);
field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
+ field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L
+ field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L
+ field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final String BLOCK_REASON = "block_reason";
field public static final int BLOCK_REASON_BLOCKED_NUMBER = 3; // 0x3
@@ -37452,6 +37457,8 @@
field public static final String IS_READ = "is_read";
field public static final String LAST_MODIFIED = "last_modified";
field public static final String LIMIT_PARAM_KEY = "limit";
+ field public static final String MISSED_REASON = "missed_reason";
+ field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L
field public static final int MISSED_TYPE = 3; // 0x3
field public static final String NEW = "new";
field public static final String NUMBER = "number";
@@ -37468,6 +37475,13 @@
field public static final int REJECTED_TYPE = 5; // 0x5
field public static final String TRANSCRIPTION = "transcription";
field public static final String TYPE = "type";
+ field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L
+ field public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 2097152L; // 0x200000L
+ field public static final long USER_MISSED_DND_MODE = 262144L; // 0x40000L
+ field public static final long USER_MISSED_LOW_RING_VOLUME = 524288L; // 0x80000L
+ field public static final long USER_MISSED_NO_ANSWER = 65536L; // 0x10000L
+ field public static final long USER_MISSED_NO_VIBRATE = 1048576L; // 0x100000L
+ field public static final long USER_MISSED_SHORT_RING = 131072L; // 0x20000L
field public static final String VIA_NUMBER = "via_number";
field public static final int VOICEMAIL_TYPE = 4; // 0x4
field public static final String VOICEMAIL_URI = "voicemail_uri";
@@ -41976,6 +41990,7 @@
field public static final int TYPE_RANGE = 2; // 0x2
field public static final int TYPE_STATELESS = 8; // 0x8
field public static final int TYPE_TEMPERATURE = 7; // 0x7
+ field public static final int TYPE_THUMBNAIL = 3; // 0x3
field public static final int TYPE_TOGGLE = 1; // 0x1
field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6
}
@@ -42015,6 +42030,14 @@
field public static final int MODE_UNKNOWN = 0; // 0x0
}
+ public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
+ ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
+ method @NonNull public CharSequence getContentDescription();
+ method public int getTemplateType();
+ method @NonNull public android.graphics.drawable.Icon getThumbnail();
+ method public boolean isActive();
+ }
+
public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate);
ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate);
@@ -44612,6 +44635,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
+ method public boolean hasCompanionInCallServiceAccess();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
@@ -46009,6 +46033,7 @@
method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
+ method public int getNrState();
method @Nullable public String getRegisteredPlmn();
method public int getTransportType();
method public boolean isRegistered();
@@ -51873,7 +51898,6 @@
}
public interface OnReceiveContentCallback<T extends android.view.View> {
- method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T);
method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
}
@@ -52451,7 +52475,7 @@
method @IdRes public int getNextFocusRightId();
method @IdRes public int getNextFocusUpId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
- method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback();
+ method @Nullable public String[] getOnReceiveContentMimeTypes();
method @ColorInt public int getOutlineAmbientShadowColor();
method public android.view.ViewOutlineProvider getOutlineProvider();
method @ColorInt public int getOutlineSpotShadowColor();
@@ -52646,6 +52670,7 @@
method public void onProvideContentCaptureStructure(@NonNull android.view.ViewStructure, int);
method public void onProvideStructure(android.view.ViewStructure);
method public void onProvideVirtualStructure(android.view.ViewStructure);
+ method public boolean onReceiveContent(@NonNull android.view.OnReceiveContentCallback.Payload);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method @CallSuper protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
@@ -52803,7 +52828,7 @@
method public void setOnHoverListener(android.view.View.OnHoverListener);
method public void setOnKeyListener(android.view.View.OnKeyListener);
method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener);
- method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>);
+ method public void setOnReceiveContentCallback(@Nullable String[], @Nullable android.view.OnReceiveContentCallback);
method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
method public void setOnTouchListener(android.view.View.OnTouchListener);
@@ -59633,7 +59658,6 @@
method public int getMinWidth();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
- method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback();
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public String getPrivateImeOptions();
@@ -59822,7 +59846,6 @@
public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
ctor public TextViewOnReceiveContentCallback();
- method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView);
method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
}
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 79c8269..b2d13f9 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -403,6 +403,7 @@
field public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats";
field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
+ field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
field public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
@@ -865,7 +866,6 @@
field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2
field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0
field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
- field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
@@ -6635,6 +6635,7 @@
method @NonNull public int[] getTransportTypes();
method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
+ field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
}
@@ -10659,6 +10660,7 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
+ method public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -10692,6 +10694,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+ method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
@@ -10731,6 +10734,11 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -10763,6 +10771,9 @@
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
+ field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
diff --git a/packages/CarSystemUI/res/values-af/strings.xml b/packages/CarSystemUI/res/values-af/strings.xml
index b377ec4..cf288d7 100644
--- a/packages/CarSystemUI/res/values-af/strings.xml
+++ b/packages/CarSystemUI/res/values-af/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Enige gebruiker kan programme vir al die ander gebruikers opdateer."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laai tans"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Laai tans gebruiker (van <xliff:g id="FROM_USER">%1$d</xliff:g> na <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Maak toe"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-am/strings.xml b/packages/CarSystemUI/res/values-am/strings.xml
index 4f2bba8..8281631 100644
--- a/packages/CarSystemUI/res/values-am/strings.xml
+++ b/packages/CarSystemUI/res/values-am/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ማንኛውም ተጠቃሚ መተግበሪያዎችን ለሌሎች ተጠቃሚዎች ሁሉ ማዘመን ይችላል።"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"በመጫን ላይ"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ተጠቃሚን (ከ<xliff:g id="FROM_USER">%1$d</xliff:g> ወደ <xliff:g id="TO_USER">%2$d</xliff:g>) በመጫን ላይ"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ዝጋ"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-as/strings.xml b/packages/CarSystemUI/res/values-as/strings.xml
index edc3621..d871055 100644
--- a/packages/CarSystemUI/res/values-as/strings.xml
+++ b/packages/CarSystemUI/res/values-as/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"যিকোনো ব্যৱহাৰকাৰীয়ে অন্য ব্যৱহাৰকাৰীৰ বাবে এপ্সমূহ আপডে’ট কৰিব পাৰে।"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ল’ড হৈ আছে"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ব্যৱহাৰকাৰী ল’ড হৈ আছে (<xliff:g id="FROM_USER">%1$d</xliff:g>ৰ পৰা to <xliff:g id="TO_USER">%2$d</xliff:g>লৈ)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"বন্ধ কৰক"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-az/strings.xml b/packages/CarSystemUI/res/values-az/strings.xml
index 398f5c3..89c9eb4 100644
--- a/packages/CarSystemUI/res/values-az/strings.xml
+++ b/packages/CarSystemUI/res/values-az/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"İstənilən istifadəçi digər bütün istifadəçilər üçün tətbiqləri güncəlləyə bilər."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Yüklənir"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"İstifadəçi yüklənir (<xliff:g id="FROM_USER">%1$d</xliff:g>-<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Qapadın"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml b/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml
index 6c1979f..6aee013 100644
--- a/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Svaki korisnik može da ažurira aplikacije za sve ostale korisnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Učitava se"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Profil korisnika se učitava (iz<xliff:g id="FROM_USER">%1$d</xliff:g> u <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-be/strings.xml b/packages/CarSystemUI/res/values-be/strings.xml
index 4e97948..fde4273 100644
--- a/packages/CarSystemUI/res/values-be/strings.xml
+++ b/packages/CarSystemUI/res/values-be/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Кожны карыстальнік прылады можа абнаўляць праграмы для ўсіх іншых карыстальнікаў."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ідзе загрузка"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ідзе загрузка профілю карыстальніка (ад <xliff:g id="FROM_USER">%1$d</xliff:g> да <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрыць"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-bg/strings.xml b/packages/CarSystemUI/res/values-bg/strings.xml
index 7dfab54..25f2845 100644
--- a/packages/CarSystemUI/res/values-bg/strings.xml
+++ b/packages/CarSystemUI/res/values-bg/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Всеки потребител може да актуализира приложенията за всички останали потребители."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Зарежда се"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Потребителят се зарежда (от <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затваряне"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-bs/strings.xml b/packages/CarSystemUI/res/values-bs/strings.xml
index 119f2d7b..588771e 100644
--- a/packages/CarSystemUI/res/values-bs/strings.xml
+++ b/packages/CarSystemUI/res/values-bs/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bilo koji korisnik može ažurirati aplikacije za sve druge korisnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Učitavanje"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Učitavanje korisnika (od korisnika <xliff:g id="FROM_USER">%1$d</xliff:g> do korisnika <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ca/strings.xml b/packages/CarSystemUI/res/values-ca/strings.xml
index b1e722e..cbd469b 100644
--- a/packages/CarSystemUI/res/values-ca/strings.xml
+++ b/packages/CarSystemUI/res/values-ca/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualsevol usuari pot actualitzar les aplicacions de la resta d\'usuaris."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"S\'està carregant"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"S\'està carregant l\'usuari (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tanca"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-cs/strings.xml b/packages/CarSystemUI/res/values-cs/strings.xml
index dd4472f..7657e32 100644
--- a/packages/CarSystemUI/res/values-cs/strings.xml
+++ b/packages/CarSystemUI/res/values-cs/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Každý uživatel může aktualizovat aplikace všech ostatních uživatelů."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Načítání"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Načítání uživatele (předchozí: <xliff:g id="FROM_USER">%1$d</xliff:g>, následující: <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zavřít"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-da/strings.xml b/packages/CarSystemUI/res/values-da/strings.xml
index 6c08aa5..120929e 100644
--- a/packages/CarSystemUI/res/values-da/strings.xml
+++ b/packages/CarSystemUI/res/values-da/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Alle brugere kan opdatere apps for alle andre brugere."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Indlæser"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Indlæser bruger (fra <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Luk"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-el/strings.xml b/packages/CarSystemUI/res/values-el/strings.xml
index 66f8d18..9b24fa4 100644
--- a/packages/CarSystemUI/res/values-el/strings.xml
+++ b/packages/CarSystemUI/res/values-el/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Οποιοσδήποτε χρήστης μπορεί να ενημερώσει τις εφαρμογές για όλους τους άλλους χρήστες."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Φόρτωση"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Φόρτωση χρήστη (από <xliff:g id="FROM_USER">%1$d</xliff:g> έως <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Κλείσιμο"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rAU/strings.xml b/packages/CarSystemUI/res/values-en-rAU/strings.xml
index b3e358f..8eb76c2 100644
--- a/packages/CarSystemUI/res/values-en-rAU/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rAU/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rCA/strings.xml b/packages/CarSystemUI/res/values-en-rCA/strings.xml
index b3e358f..8eb76c2 100644
--- a/packages/CarSystemUI/res/values-en-rCA/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rCA/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rGB/strings.xml b/packages/CarSystemUI/res/values-en-rGB/strings.xml
index b3e358f..8eb76c2 100644
--- a/packages/CarSystemUI/res/values-en-rGB/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rGB/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rIN/strings.xml b/packages/CarSystemUI/res/values-en-rIN/strings.xml
index b3e358f..8eb76c2 100644
--- a/packages/CarSystemUI/res/values-en-rIN/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rIN/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rXC/strings.xml b/packages/CarSystemUI/res/values-en-rXC/strings.xml
index eaf6f51..37a568b 100644
--- a/packages/CarSystemUI/res/values-en-rXC/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rXC/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-es-rUS/strings.xml b/packages/CarSystemUI/res/values-es-rUS/strings.xml
index 6a5f8ce..16aba86 100644
--- a/packages/CarSystemUI/res/values-es-rUS/strings.xml
+++ b/packages/CarSystemUI/res/values-es-rUS/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Cualquier usuario podrá actualizar las apps de otras personas."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Cargando"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Cargando usuario (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Cerrar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-es/strings.xml b/packages/CarSystemUI/res/values-es/strings.xml
index c43d7e5..8aad2ca 100644
--- a/packages/CarSystemUI/res/values-es/strings.xml
+++ b/packages/CarSystemUI/res/values-es/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Cualquier usuario puede actualizar las aplicaciones del resto de los usuarios."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Cargando"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Cargando usuario (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Cerrar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-et/strings.xml b/packages/CarSystemUI/res/values-et/strings.xml
index ad82d5f..14ec9df 100644
--- a/packages/CarSystemUI/res/values-et/strings.xml
+++ b/packages/CarSystemUI/res/values-et/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Iga kasutaja saab rakendusi värskendada kõigi teiste kasutajate jaoks."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laadimine"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Kasutaja laadimine (<xliff:g id="FROM_USER">%1$d</xliff:g> > <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sule"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fa/strings.xml b/packages/CarSystemUI/res/values-fa/strings.xml
index ef37b65..3f53b11 100644
--- a/packages/CarSystemUI/res/values-fa/strings.xml
+++ b/packages/CarSystemUI/res/values-fa/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"هر کاربری میتواند برنامهها را برای همه کاربران دیگر بهروزرسانی کند."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"درحال بارگیری"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"بارگیری کاربر (از <xliff:g id="FROM_USER">%1$d</xliff:g> تا <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"بستن"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fi/strings.xml b/packages/CarSystemUI/res/values-fi/strings.xml
index 10bb0c5..79b53f6 100644
--- a/packages/CarSystemUI/res/values-fi/strings.xml
+++ b/packages/CarSystemUI/res/values-fi/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Kaikki käyttäjät voivat päivittää muiden käyttäjien sovelluksia."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ladataan"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ladataan käyttäjäprofiilia (<xliff:g id="FROM_USER">%1$d</xliff:g>–<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sulje"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fr-rCA/strings.xml b/packages/CarSystemUI/res/values-fr-rCA/strings.xml
index bebd3f4..b190549 100644
--- a/packages/CarSystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/CarSystemUI/res/values-fr-rCA/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Tout utilisateur peut mettre à jour les applications pour tous les autres utilisateurs."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Chargement en cours…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Chargement de l\'utilisateur (de <xliff:g id="FROM_USER">%1$d</xliff:g> vers <xliff:g id="TO_USER">%2$d</xliff:g>) en cours…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fermer"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fr/strings.xml b/packages/CarSystemUI/res/values-fr/strings.xml
index 3d498d2..5a905a0 100644
--- a/packages/CarSystemUI/res/values-fr/strings.xml
+++ b/packages/CarSystemUI/res/values-fr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"N\'importe quel utilisateur peut mettre à jour les applications pour tous les autres utilisateurs."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Chargement…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Chargement de l\'utilisateur (de <xliff:g id="FROM_USER">%1$d</xliff:g> à <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fermer"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hi/strings.xml b/packages/CarSystemUI/res/values-hi/strings.xml
index 95454a5..83321fd 100644
--- a/packages/CarSystemUI/res/values-hi/strings.xml
+++ b/packages/CarSystemUI/res/values-hi/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"कोई भी उपयोगकर्ता, बाकी सभी उपयोगकर्ताओं के लिए ऐप्लिकेशन अपडेट कर सकता है."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"लोड हो रही है"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"उपयोगकर्ता को लोड किया जा रहा है (<xliff:g id="FROM_USER">%1$d</xliff:g> से <xliff:g id="TO_USER">%2$d</xliff:g> पर)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"बंद करें"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hr/strings.xml b/packages/CarSystemUI/res/values-hr/strings.xml
index f3aaf63..872fc69 100644
--- a/packages/CarSystemUI/res/values-hr/strings.xml
+++ b/packages/CarSystemUI/res/values-hr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Svaki korisnik može ažurirati aplikacije za ostale korisnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Učitavanje"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Učitavanje korisnika (od <xliff:g id="FROM_USER">%1$d</xliff:g> do <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hu/strings.xml b/packages/CarSystemUI/res/values-hu/strings.xml
index b63ba8b..63328f3 100644
--- a/packages/CarSystemUI/res/values-hu/strings.xml
+++ b/packages/CarSystemUI/res/values-hu/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bármely felhasználó frissítheti az alkalmazásokat az összes felhasználó számára."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Betöltés"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Felhasználó betöltése (<xliff:g id="FROM_USER">%1$d</xliff:g> → <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Bezárás"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hy/strings.xml b/packages/CarSystemUI/res/values-hy/strings.xml
index e2a2c6b..778f695 100644
--- a/packages/CarSystemUI/res/values-hy/strings.xml
+++ b/packages/CarSystemUI/res/values-hy/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Ցանկացած օգտատեր կարող է թարմացնել հավելվածները բոլոր մյուս հաշիվների համար։"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Բեռնում"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Օգտատերը բեռնվում է (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Փակել"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-in/strings.xml b/packages/CarSystemUI/res/values-in/strings.xml
index 0a70d26..386d79e 100644
--- a/packages/CarSystemUI/res/values-in/strings.xml
+++ b/packages/CarSystemUI/res/values-in/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Setiap pengguna dapat mengupdate aplikasi untuk semua pengguna lain."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Memuat"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Memuat pengguna (dari <xliff:g id="FROM_USER">%1$d</xliff:g> menjadi <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tutup"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-is/strings.xml b/packages/CarSystemUI/res/values-is/strings.xml
index ea6b031..5a927aa0 100644
--- a/packages/CarSystemUI/res/values-is/strings.xml
+++ b/packages/CarSystemUI/res/values-is/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Allir notendur geta uppfært forrit fyrir alla aðra notendur."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Hleður"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Hleður notanda (frá <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Loka"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-it/strings.xml b/packages/CarSystemUI/res/values-it/strings.xml
index ecbcd5d..41d7ad4 100644
--- a/packages/CarSystemUI/res/values-it/strings.xml
+++ b/packages/CarSystemUI/res/values-it/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualsiasi utente può aggiornare le app per tutti gli altri."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Caricamento"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Caricamento dell\'utente (da <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Chiudi"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-iw/strings.xml b/packages/CarSystemUI/res/values-iw/strings.xml
index fe182a3..f419cac 100644
--- a/packages/CarSystemUI/res/values-iw/strings.xml
+++ b/packages/CarSystemUI/res/values-iw/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"כל משתמש יכול לעדכן אפליקציות לכל שאר המשתמשים."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"בטעינה"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"המשתמש בטעינה (מהמשתמש <xliff:g id="FROM_USER">%1$d</xliff:g> אל <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"סגירה"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ja/strings.xml b/packages/CarSystemUI/res/values-ja/strings.xml
index 1448675..9cf056fd 100644
--- a/packages/CarSystemUI/res/values-ja/strings.xml
+++ b/packages/CarSystemUI/res/values-ja/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"どのユーザーも他のすべてのユーザーに代わってアプリを更新できます。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"読み込んでいます"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ユーザーを読み込んでいます(<xliff:g id="FROM_USER">%1$d</xliff:g>~<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"閉じる"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ka/strings.xml b/packages/CarSystemUI/res/values-ka/strings.xml
index 0fef7e5..7d62c62 100644
--- a/packages/CarSystemUI/res/values-ka/strings.xml
+++ b/packages/CarSystemUI/res/values-ka/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ნებისმიერ მომხმარებელს შეუძლია აპები ყველა სხვა მომხმარებლისათვის განაახლოს."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"იტვირთება"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"იტვირთება მომხმარებელი (<xliff:g id="FROM_USER">%1$d</xliff:g>-დან <xliff:g id="TO_USER">%2$d</xliff:g>-მდე)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"დახურვა"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-kk/strings.xml b/packages/CarSystemUI/res/values-kk/strings.xml
index a4cf787..2dd1b66 100644
--- a/packages/CarSystemUI/res/values-kk/strings.xml
+++ b/packages/CarSystemUI/res/values-kk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Кез келген пайдаланушы қолданбаларды басқа пайдаланушылар үшін жаңарта алады."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Жүктелуде"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Пайдаланушы профилі жүктелуде (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Жабу"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-km/strings.xml b/packages/CarSystemUI/res/values-km/strings.xml
index 7b9a093..709cfe5 100644
--- a/packages/CarSystemUI/res/values-km/strings.xml
+++ b/packages/CarSystemUI/res/values-km/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"អ្នកប្រើប្រាស់ណាក៏អាចដំឡើងកំណែកម្មវិធីសម្រាប់អ្នកប្រើប្រាស់ទាំងអស់ផ្សេងទៀតបានដែរ។"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"កំពុងផ្ទុក"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"កំពុងផ្ទុកអ្នកប្រើប្រាស់ (ពី <xliff:g id="FROM_USER">%1$d</xliff:g> ដល់ <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"បិទ"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ko/strings.xml b/packages/CarSystemUI/res/values-ko/strings.xml
index b570c5c..17a2466 100644
--- a/packages/CarSystemUI/res/values-ko/strings.xml
+++ b/packages/CarSystemUI/res/values-ko/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"누구나 다른 모든 사용자를 위해 앱을 업데이트할 수 있습니다."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"로드 중"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"사용자 로드 중(<xliff:g id="FROM_USER">%1$d</xliff:g>님에서 <xliff:g id="TO_USER">%2$d</xliff:g>님으로)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"닫기"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ky/strings.xml b/packages/CarSystemUI/res/values-ky/strings.xml
index c66b34f..dd9225a 100644
--- a/packages/CarSystemUI/res/values-ky/strings.xml
+++ b/packages/CarSystemUI/res/values-ky/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Колдонмолорду бир колдонуучу калган бардык колдонуучулар үчүн да жаңырта алат."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Жүктөлүүдө"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Колдонуучу тууралуу маалымат жүктөлүүдө (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Жабуу"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-lo/strings.xml b/packages/CarSystemUI/res/values-lo/strings.xml
index 2bf19e0..bc94a51 100644
--- a/packages/CarSystemUI/res/values-lo/strings.xml
+++ b/packages/CarSystemUI/res/values-lo/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ຜູ້ໃຊ້ຕ່າງໆສາມາດອັບເດດແອັບສຳລັບຜູ້ໃຊ້ອື່ນທັງໝົດໄດ້."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ກຳລັງໂຫຼດ"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ກຳລັງໂຫຼດຜູ້ໃຊ້ (ຈາກ <xliff:g id="FROM_USER">%1$d</xliff:g> ໄປຍັງ <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ປິດ"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-lt/strings.xml b/packages/CarSystemUI/res/values-lt/strings.xml
index 1cae1e9..a47ad59 100644
--- a/packages/CarSystemUI/res/values-lt/strings.xml
+++ b/packages/CarSystemUI/res/values-lt/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bet kuris naudotojas gali atnaujinti visų kitų naudotojų programas."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Įkeliama"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Įkeliamas naudotojo profilis (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Uždaryti"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-lv/strings.xml b/packages/CarSystemUI/res/values-lv/strings.xml
index 62b8bf8..cb7c8b9 100644
--- a/packages/CarSystemUI/res/values-lv/strings.xml
+++ b/packages/CarSystemUI/res/values-lv/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Ikviens lietotājs var atjaunināt lietotnes visu lietotāju vārdā."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Notiek ielāde…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Notiek lietotāja profila ielāde (<xliff:g id="FROM_USER">%1$d</xliff:g>–<xliff:g id="TO_USER">%2$d</xliff:g>)…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Aizvērt"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-mk/strings.xml b/packages/CarSystemUI/res/values-mk/strings.xml
index 3e7ad63..cd2ae97 100644
--- a/packages/CarSystemUI/res/values-mk/strings.xml
+++ b/packages/CarSystemUI/res/values-mk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Секој корисник може да ажурира апликации за сите други корисници."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Се вчитува"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Се вчитува корисникот (од <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затвори"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-mn/strings.xml b/packages/CarSystemUI/res/values-mn/strings.xml
index 45921d2..33bcd27 100644
--- a/packages/CarSystemUI/res/values-mn/strings.xml
+++ b/packages/CarSystemUI/res/values-mn/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Бусад бүх хэрэглэгчийн аппыг дурын хэрэглэгч шинэчлэх боломжтой."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ачаалж байна"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Хэрэглэгчийг ачаалж байна (<xliff:g id="FROM_USER">%1$d</xliff:g>-с <xliff:g id="TO_USER">%2$d</xliff:g> хүртэл)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Хаах"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ms/strings.xml b/packages/CarSystemUI/res/values-ms/strings.xml
index 1a43d9c..0bb683b 100644
--- a/packages/CarSystemUI/res/values-ms/strings.xml
+++ b/packages/CarSystemUI/res/values-ms/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Mana-mana pengguna boleh mengemas kini apl untuk semua pengguna lain."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Memuatkan"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Memuatkan pengguna (daripada <xliff:g id="FROM_USER">%1$d</xliff:g> hingga <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tutup"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-my/strings.xml b/packages/CarSystemUI/res/values-my/strings.xml
index 4f3922b..4e7ca39 100644
--- a/packages/CarSystemUI/res/values-my/strings.xml
+++ b/packages/CarSystemUI/res/values-my/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"မည်သူမဆို အသုံးပြုသူအားလုံးအတွက် အက်ပ်များကို အပ်ဒိတ်လုပ်နိုင်သည်။"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ဖွင့်နေသည်"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"အသုံးပြုသူကို ဖွင့်နေသည် (<xliff:g id="FROM_USER">%1$d</xliff:g> မှ <xliff:g id="TO_USER">%2$d</xliff:g> သို့)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ပိတ်ရန်"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-nb/strings.xml b/packages/CarSystemUI/res/values-nb/strings.xml
index 5b6166f..0b2856f 100644
--- a/packages/CarSystemUI/res/values-nb/strings.xml
+++ b/packages/CarSystemUI/res/values-nb/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Alle brukere kan oppdatere apper for alle andre brukere."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laster inn"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Laster inn brukeren (fra <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Lukk"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-nl/strings.xml b/packages/CarSystemUI/res/values-nl/strings.xml
index d79f2b1..4765f71 100644
--- a/packages/CarSystemUI/res/values-nl/strings.xml
+++ b/packages/CarSystemUI/res/values-nl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Elke gebruiker kan apps updaten voor alle andere gebruikers"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laden"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Gebruiker laden (van <xliff:g id="FROM_USER">%1$d</xliff:g> naar <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sluiten"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-pl/strings.xml b/packages/CarSystemUI/res/values-pl/strings.xml
index dd8c189..52b90f1 100644
--- a/packages/CarSystemUI/res/values-pl/strings.xml
+++ b/packages/CarSystemUI/res/values-pl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Każdy użytkownik może aktualizować aplikacje wszystkich innych użytkowników."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ładuję"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ładuję użytkownika (od <xliff:g id="FROM_USER">%1$d</xliff:g> do <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zamknij"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-pt-rPT/strings.xml b/packages/CarSystemUI/res/values-pt-rPT/strings.xml
index c7f5ecf..2dffa17 100644
--- a/packages/CarSystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/CarSystemUI/res/values-pt-rPT/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualquer utilizador pode atualizar apps para todos os outros utilizadores."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"A carregar…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"A carregar o utilizador (de <xliff:g id="FROM_USER">%1$d</xliff:g> para <xliff:g id="TO_USER">%2$d</xliff:g>)…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fechar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-pt/strings.xml b/packages/CarSystemUI/res/values-pt/strings.xml
index 0712fb8..a7c44d2 100644
--- a/packages/CarSystemUI/res/values-pt/strings.xml
+++ b/packages/CarSystemUI/res/values-pt/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualquer usuário pode atualizar apps para os demais usuários."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Carregando"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Carregando usuário (de <xliff:g id="FROM_USER">%1$d</xliff:g> para <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fechar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ro/strings.xml b/packages/CarSystemUI/res/values-ro/strings.xml
index 60fd4fe..1a4e71d 100644
--- a/packages/CarSystemUI/res/values-ro/strings.xml
+++ b/packages/CarSystemUI/res/values-ro/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Orice utilizator poate actualiza aplicațiile pentru toți ceilalți utilizatori."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Se încarcă"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Se încarcă utilizatorul (de la <xliff:g id="FROM_USER">%1$d</xliff:g> la <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Închideți"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ru/strings.xml b/packages/CarSystemUI/res/values-ru/strings.xml
index a043d24..330ba2f 100644
--- a/packages/CarSystemUI/res/values-ru/strings.xml
+++ b/packages/CarSystemUI/res/values-ru/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Любой пользователь устройства может обновлять приложения для всех аккаунтов."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Загрузка…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Загрузка профиля пользователя (с <xliff:g id="FROM_USER">%1$d</xliff:g> по <xliff:g id="TO_USER">%2$d</xliff:g>)…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрыть"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-si/strings.xml b/packages/CarSystemUI/res/values-si/strings.xml
index e14f64a..6391d28 100644
--- a/packages/CarSystemUI/res/values-si/strings.xml
+++ b/packages/CarSystemUI/res/values-si/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"සියලුම අනෙක් පරිශීලකයින් සඳහා ඕනෑම පරිශීලකයෙකුට යෙදුම් යාවත්කාලීන කළ හැක."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"පූරණය වෙමින්"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"පරිශීලකයා පූරණය වෙමින් (<xliff:g id="FROM_USER">%1$d</xliff:g> සිට <xliff:g id="TO_USER">%2$d</xliff:g> වෙත)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"වසන්න"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sk/strings.xml b/packages/CarSystemUI/res/values-sk/strings.xml
index 8f98983..b100a5d4 100644
--- a/packages/CarSystemUI/res/values-sk/strings.xml
+++ b/packages/CarSystemUI/res/values-sk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Ktorýkoľvek používateľ môže aktualizovať aplikácie všetkých ostatných používateľov."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Načítava sa"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Načítava sa používateľ (predchádzajúci: <xliff:g id="FROM_USER">%1$d</xliff:g>, nasledujúci: <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zavrieť"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sl/strings.xml b/packages/CarSystemUI/res/values-sl/strings.xml
index 6b47184..b67002b 100644
--- a/packages/CarSystemUI/res/values-sl/strings.xml
+++ b/packages/CarSystemUI/res/values-sl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Vsak uporabnik lahko posodobi aplikacije za vse druge uporabnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Nalaganje"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Nalaganje uporabnika (od uporabnika <xliff:g id="FROM_USER">%1$d</xliff:g> do uporabnika <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zapri"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sq/strings.xml b/packages/CarSystemUI/res/values-sq/strings.xml
index 18ea401..d19e158 100644
--- a/packages/CarSystemUI/res/values-sq/strings.xml
+++ b/packages/CarSystemUI/res/values-sq/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Çdo përdorues mund t\'i përditësojë aplikacionet për të gjithë përdoruesit e tjerë."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Po ngarkohet"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Përdoruesi po ngarkohet (nga <xliff:g id="FROM_USER">%1$d</xliff:g> te <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Mbyll"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sr/strings.xml b/packages/CarSystemUI/res/values-sr/strings.xml
index 878a1c1..a5fb5b4 100644
--- a/packages/CarSystemUI/res/values-sr/strings.xml
+++ b/packages/CarSystemUI/res/values-sr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Сваки корисник може да ажурира апликације за све остале кориснике."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Учитава се"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Профил корисника се учитава (из<xliff:g id="FROM_USER">%1$d</xliff:g> у <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затвори"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sv/strings.xml b/packages/CarSystemUI/res/values-sv/strings.xml
index 905dd44..8a942d6 100644
--- a/packages/CarSystemUI/res/values-sv/strings.xml
+++ b/packages/CarSystemUI/res/values-sv/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Alla användare kan uppdatera appar för andra användare."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Läser in"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Läser in användare (från <xliff:g id="FROM_USER">%1$d</xliff:g> till <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Stäng"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sw/strings.xml b/packages/CarSystemUI/res/values-sw/strings.xml
index 3cb6098..be03373 100644
--- a/packages/CarSystemUI/res/values-sw/strings.xml
+++ b/packages/CarSystemUI/res/values-sw/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Mtumiaji yeyote anaweza kusasisha programu za watumiaji wengine."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Inapakia"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Inapakia wasifu wa mtumiaji (kutoka <xliff:g id="FROM_USER">%1$d</xliff:g> kuwa <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Funga"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ta/strings.xml b/packages/CarSystemUI/res/values-ta/strings.xml
index de52ede..a82a2f8 100644
--- a/packages/CarSystemUI/res/values-ta/strings.xml
+++ b/packages/CarSystemUI/res/values-ta/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"எந்தப் பயனரும் பிற பயனர்கள் சார்பாக ஆப்ஸைப் புதுப்பிக்க முடியும்."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ஏற்றுகிறது"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"பயனர் தகவலை ஏற்றுகிறது (<xliff:g id="FROM_USER">%1$d</xliff:g>லிருந்து <xliff:g id="TO_USER">%2$d</xliff:g> வரை)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"மூடுக"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-th/strings.xml b/packages/CarSystemUI/res/values-th/strings.xml
index 8f3012b..dacf605 100644
--- a/packages/CarSystemUI/res/values-th/strings.xml
+++ b/packages/CarSystemUI/res/values-th/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ผู้ใช้ทุกคนจะอัปเดตแอปให้แก่ผู้ใช้คนอื่นๆ ได้"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"กำลังโหลด"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"กำลังโหลดผู้ใช้ (จาก <xliff:g id="FROM_USER">%1$d</xliff:g> ถึง <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ปิด"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-tl/strings.xml b/packages/CarSystemUI/res/values-tl/strings.xml
index 5b0e3b3..89d8cd2 100644
--- a/packages/CarSystemUI/res/values-tl/strings.xml
+++ b/packages/CarSystemUI/res/values-tl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Puwedeng i-update ng sinumang user ang mga app para sa lahat ng iba pang user."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Naglo-load"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Nilo-load ang user (mula kay <xliff:g id="FROM_USER">%1$d</xliff:g> papunta kay <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Isara"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-tr/strings.xml b/packages/CarSystemUI/res/values-tr/strings.xml
index 81fa01c..36bf694 100644
--- a/packages/CarSystemUI/res/values-tr/strings.xml
+++ b/packages/CarSystemUI/res/values-tr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Herhangi bir kullanıcı, diğer tüm kullanıcılar için uygulamaları güncelleyebilir."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Yükleniyor"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Kullanıcı yükleniyor (<xliff:g id="FROM_USER">%1$d</xliff:g> kullanıcısından <xliff:g id="TO_USER">%2$d</xliff:g> kullanıcısına)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Kapat"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-uk/strings.xml b/packages/CarSystemUI/res/values-uk/strings.xml
index b7031c6..391513f 100644
--- a/packages/CarSystemUI/res/values-uk/strings.xml
+++ b/packages/CarSystemUI/res/values-uk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Усі користувачі можуть оновлювати додатки для решти людей."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Завантаження"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Завантаження профілю користувача (від <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрити"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-uz/strings.xml b/packages/CarSystemUI/res/values-uz/strings.xml
index 471e459..398d1f5 100644
--- a/packages/CarSystemUI/res/values-uz/strings.xml
+++ b/packages/CarSystemUI/res/values-uz/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qurilmaning istalgan foydalanuvchisi ilovalarni barcha hisoblar uchun yangilashi mumkin."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Yuklanmoqda"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Foydalanuvchi profili yuklanmoqda (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Yopish"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-vi/strings.xml b/packages/CarSystemUI/res/values-vi/strings.xml
index 26bdddc..f15320f 100644
--- a/packages/CarSystemUI/res/values-vi/strings.xml
+++ b/packages/CarSystemUI/res/values-vi/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bất kỳ người dùng nào cũng có thể cập nhật ứng dụng cho tất cả những người dùng khác."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Đang tải"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Đang tải hồ sơ người dùng (từ <xliff:g id="FROM_USER">%1$d</xliff:g> sang <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Đóng"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zh-rCN/strings.xml b/packages/CarSystemUI/res/values-zh-rCN/strings.xml
index e7ca871..a91f48c 100644
--- a/packages/CarSystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/CarSystemUI/res/values-zh-rCN/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"任何用户均可为所有其他用户更新应用。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"正在加载"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在加载用户(从 <xliff:g id="FROM_USER">%1$d</xliff:g> 到 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"关闭"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zh-rHK/strings.xml b/packages/CarSystemUI/res/values-zh-rHK/strings.xml
index 268243f..7aa6116 100644
--- a/packages/CarSystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/CarSystemUI/res/values-zh-rHK/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"任何使用者都可以為所有其他使用者更新應用程式。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"正在載入"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在載入使用者 (由 <xliff:g id="FROM_USER">%1$d</xliff:g> 至 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"關閉"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zh-rTW/strings.xml b/packages/CarSystemUI/res/values-zh-rTW/strings.xml
index 9dc0f1a..c062463 100644
--- a/packages/CarSystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/CarSystemUI/res/values-zh-rTW/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"任何使用者都能為所有其他使用者更新應用程式。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"載入中"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在載入使用者 (從 <xliff:g id="FROM_USER">%1$d</xliff:g> 到 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"關閉"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zu/strings.xml b/packages/CarSystemUI/res/values-zu/strings.xml
index 8845ff7..2dd33d8 100644
--- a/packages/CarSystemUI/res/values-zu/strings.xml
+++ b/packages/CarSystemUI/res/values-zu/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Noma yimuphi umsebenzisi angabuyekeza izinhlelo zokusebenza zabanye abasebenzisi."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Iyalayisha"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ilayisha umsebenzisi (kusuka ku-<xliff:g id="FROM_USER">%1$d</xliff:g> kuya ku-<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Vala"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 02f4457..9aa7cc4a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -17,6 +17,7 @@
package com.android.companiondevicemanager;
import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.app.Activity;
import android.companion.CompanionDeviceManager;
@@ -57,6 +58,8 @@
Log.e(LOG_TAG, "About to show UI, but no devices to show");
}
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
if (getService().mRequest.isSingleDevice()) {
setContentView(R.layout.device_confirmation);
final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c4b36fb..b9e30fb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -175,8 +175,8 @@
Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
Settings.Secure.TAPS_APP_TO_EXIT,
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
- Settings.Secure.PANIC_GESTURE_ENABLED,
- Settings.Secure.PANIC_SOUND_ENABLED,
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED,
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 7f694ad..721bf73 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -264,8 +264,8 @@
VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TAPS_APP_TO_EXIT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.PANIC_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 94de485..1a2c2c8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -863,9 +863,6 @@
p.end(intentFirewallToken);
dumpSetting(s, p,
- Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
- GlobalSettingsProto.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS);
- dumpSetting(s, p,
Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
GlobalSettingsProto.KEEP_PROFILE_IN_BACKGROUND);
@@ -2035,11 +2032,11 @@
final long emergencyResponseToken = p.start(SecureSettingsProto.EMERGENCY_RESPONSE);
dumpSetting(s, p,
- Settings.Secure.PANIC_GESTURE_ENABLED,
- SecureSettingsProto.EmergencyResponse.PANIC_GESTURE_ENABLED);
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ SecureSettingsProto.EmergencyResponse.EMERGENCY_GESTURE_ENABLED);
dumpSetting(s, p,
- Settings.Secure.PANIC_SOUND_ENABLED,
- SecureSettingsProto.EmergencyResponse.PANIC_SOUND_ENABLED);
+ Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED,
+ SecureSettingsProto.EmergencyResponse.EMERGENCY_GESTURE_SOUND_ENABLED);
p.end(emergencyResponseToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 10d2eab..6b4629a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -309,7 +309,6 @@
Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
- Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
Settings.Global.KERNEL_CPU_THREAD_READER,
Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4071045..ddd0dac 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -586,6 +586,8 @@
</intent-filter>
</activity>
+ <activity android:name=".people.widget.LaunchConversationActivity" />
+
<!-- People Space Widget -->
<receiver
android:name=".people.widget.PeopleSpaceWidgetProvider"
diff --git a/packages/SystemUI/res/layout/people_space_widget_item.xml b/packages/SystemUI/res/layout/people_space_widget_item.xml
index a40bfff..e4de6f9 100644
--- a/packages/SystemUI/res/layout/people_space_widget_item.xml
+++ b/packages/SystemUI/res/layout/people_space_widget_item.xml
@@ -15,12 +15,12 @@
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:background="@drawable/people_space_tile_view_card"
+ android:id="@+id/item"
android:orientation="vertical"
android:padding="6dp"
android:layout_marginBottom="6dp"
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 2bc6c90..68ef1a3 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -38,7 +38,7 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
<string name="battery_saver_start_action" msgid="4553256017945469937">"Uključi Uštedu baterije"</string>
<string name="status_bar_settings_settings_button" msgid="534331565185171556">"Podešavanja"</string>
- <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"Wi-Fi"</string>
+ <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"WiFi"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatsko rotiranje ekrana"</string>
<string name="status_bar_settings_mute_label" msgid="914392730086057522">"UGASI"</string>
<string name="status_bar_settings_auto_brightness_label" msgid="2151934479226017725">"AUTOM."</string>
@@ -225,7 +225,7 @@
<string name="data_connection_cdma" msgid="7678457855627313518">"1X"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roming"</string>
<string name="data_connection_edge" msgid="6316755666481405762">"EDGE"</string>
- <string name="accessibility_data_connection_wifi" msgid="4422160347472742434">"Wi-Fi"</string>
+ <string name="accessibility_data_connection_wifi" msgid="4422160347472742434">"WiFi"</string>
<string name="accessibility_no_sim" msgid="1140839832913084973">"Nema SIM kartice."</string>
<string name="accessibility_cell_data" msgid="172950885786007392">"Mobilni podaci"</string>
<string name="accessibility_cell_data_on" msgid="691666434519443162">"Mobilni podaci su uključeni"</string>
@@ -264,8 +264,8 @@
<string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Zaključan ekran za posao"</string>
<string name="accessibility_desc_close" msgid="8293708213442107755">"Zatvori"</string>
<string name="accessibility_quick_settings_wifi" msgid="167707325133803052">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
- <string name="accessibility_quick_settings_wifi_changed_off" msgid="2230487165558877262">"Wi-Fi je isključen."</string>
- <string name="accessibility_quick_settings_wifi_changed_on" msgid="1490362586009027611">"Wi-Fi je uključen."</string>
+ <string name="accessibility_quick_settings_wifi_changed_off" msgid="2230487165558877262">"WiFi je isključen."</string>
+ <string name="accessibility_quick_settings_wifi_changed_on" msgid="1490362586009027611">"WiFi je uključen."</string>
<string name="accessibility_quick_settings_mobile" msgid="1817825313718492906">"Mobilna mreža: <xliff:g id="SIGNAL">%1$s</xliff:g>. <xliff:g id="TYPE">%2$s</xliff:g>. <xliff:g id="NETWORK">%3$s</xliff:g>."</string>
<string name="accessibility_quick_settings_battery" msgid="533594896310663853">"Baterija: <xliff:g id="STATE">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_airplane_off" msgid="1275658769368793228">"Režim rada u avionu je isključen."</string>
@@ -374,19 +374,19 @@
<string name="quick_settings_user_label" msgid="1253515509432672496">"Ja"</string>
<string name="quick_settings_user_title" msgid="8673045967216204537">"Korisnik"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novi korisnik"</string>
- <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
+ <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Veza nije uspostavljena"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nema mreže"</string>
- <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi je isključen"</string>
- <string name="quick_settings_wifi_on_label" msgid="2489928193654318511">"Wi-Fi je uključen"</string>
- <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Nije dostupna nijedna Wi-Fi mreža"</string>
+ <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi je isključen"</string>
+ <string name="quick_settings_wifi_on_label" msgid="2489928193654318511">"WiFi je uključen"</string>
+ <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Nije dostupna nijedna WiFi mreža"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Uključuje se..."</string>
<string name="quick_settings_cast_title" msgid="2279220930629235211">"Prebacivanje ekrana"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_device_default_description" msgid="2580520859212250265">"Spremno za prebacivanje"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nije dostupan nijedan uređaj"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nije povezan"</string>
+ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="2325362583903258677">"AUTOMATSKA"</string>
<string name="quick_settings_inversion_label" msgid="5078769633069667698">"Obrni boje"</string>
@@ -642,8 +642,8 @@
<string name="output_none_found" msgid="5488087293120982770">"Nije pronađen nijedan uređaj"</string>
<string name="output_none_found_service_off" msgid="935667567681386368">"Nije pronađen nijedan uređaj. Probajte da uključite uslugu <xliff:g id="SERVICE">%1$s</xliff:g>"</string>
<string name="output_service_bt" msgid="4315362133973911687">"Bluetooth"</string>
- <string name="output_service_wifi" msgid="9003667810868222134">"Wi-Fi"</string>
- <string name="output_service_bt_wifi" msgid="7186882540475524124">"Bluetooth i Wi-Fi"</string>
+ <string name="output_service_wifi" msgid="9003667810868222134">"WiFi"</string>
+ <string name="output_service_bt_wifi" msgid="7186882540475524124">"Bluetooth i WiFi"</string>
<string name="system_ui_tuner" msgid="1471348823289954729">"Tjuner za korisnički interfejs sistema"</string>
<string name="show_battery_percentage" msgid="6235377891802910455">"Prikazuj ugrađeni procenat baterije"</string>
<string name="show_battery_percentage_summary" msgid="9053024758304102915">"Prikazivanje nivoa napunjenosti baterije u procentima unutar ikone na statusnoj traci kada se baterija ne puni"</string>
@@ -946,7 +946,7 @@
<string name="mobile_data" msgid="4564407557775397216">"Mobilni podaci"</string>
<string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
- <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi je isključen"</string>
+ <string name="wifi_is_off" msgid="5389597396308001471">"WiFi je isključen"</string>
<string name="bt_is_off" msgid="7436344904889461591">"Bluetooth je isključen"</string>
<string name="dnd_is_off" msgid="3185706903793094463">"Režim Ne uznemiravaj je isključen"</string>
<string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"Automatsko pravilo (<xliff:g id="ID_1">%s</xliff:g>) je uključilo režim Ne uznemiravaj."</string>
@@ -958,7 +958,7 @@
<string name="running_foreground_services_title" msgid="5137313173431186685">"Aplikacije pokrenute u pozadini"</string>
<string name="running_foreground_services_msg" msgid="3009459259222695385">"Dodirnite za detalje o bateriji i potrošnji podataka"</string>
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite da isključite mobilne podatke?"</string>
- <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ili internetu preko mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo preko Wi-Fi veze."</string>
+ <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ili internetu preko mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo preko WiFi veze."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mobilni operater"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Podešavanja ne mogu da verifikuju vaš odgovor jer aplikacija skriva zahtev za dozvolu."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index fe38ea0..afa0759 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -605,7 +605,7 @@
<string name="screen_pinning_positive" msgid="3285785989665266984">"متوجه شدم"</string>
<string name="screen_pinning_negative" msgid="6882816864569211666">"نه متشکرم"</string>
<string name="screen_pinning_start" msgid="7483998671383371313">"برنامه پین شد"</string>
- <string name="screen_pinning_exit" msgid="4553787518387346893">"پین برنامه برداشته شد"</string>
+ <string name="screen_pinning_exit" msgid="4553787518387346893">"سنجاق از برنامه برداشته شد"</string>
<string name="quick_settings_reset_confirmation_title" msgid="463533331480997595">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> مخفی شود؟"</string>
<string name="quick_settings_reset_confirmation_message" msgid="2320586180785674186">"دفعه بعد که آن را روشن کنید، در تنظیمات نشان داده میشود."</string>
<string name="quick_settings_reset_confirmation_button" msgid="3341477479055016776">"پنهان کردن"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index cd95885..1ad7a91 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -38,7 +38,7 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Укључи"</string>
<string name="battery_saver_start_action" msgid="4553256017945469937">"Укључи Уштеду батерије"</string>
<string name="status_bar_settings_settings_button" msgid="534331565185171556">"Подешавања"</string>
- <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"Wi-Fi"</string>
+ <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"WiFi"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Аутоматско ротирање екрана"</string>
<string name="status_bar_settings_mute_label" msgid="914392730086057522">"УГАСИ"</string>
<string name="status_bar_settings_auto_brightness_label" msgid="2151934479226017725">"АУТОM."</string>
@@ -225,7 +225,7 @@
<string name="data_connection_cdma" msgid="7678457855627313518">"1X"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роминг"</string>
<string name="data_connection_edge" msgid="6316755666481405762">"EDGE"</string>
- <string name="accessibility_data_connection_wifi" msgid="4422160347472742434">"Wi-Fi"</string>
+ <string name="accessibility_data_connection_wifi" msgid="4422160347472742434">"WiFi"</string>
<string name="accessibility_no_sim" msgid="1140839832913084973">"Нема SIM картице."</string>
<string name="accessibility_cell_data" msgid="172950885786007392">"Мобилни подаци"</string>
<string name="accessibility_cell_data_on" msgid="691666434519443162">"Мобилни подаци су укључени"</string>
@@ -264,8 +264,8 @@
<string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Закључан екран за посао"</string>
<string name="accessibility_desc_close" msgid="8293708213442107755">"Затвори"</string>
<string name="accessibility_quick_settings_wifi" msgid="167707325133803052">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
- <string name="accessibility_quick_settings_wifi_changed_off" msgid="2230487165558877262">"Wi-Fi је искључен."</string>
- <string name="accessibility_quick_settings_wifi_changed_on" msgid="1490362586009027611">"Wi-Fi је укључен."</string>
+ <string name="accessibility_quick_settings_wifi_changed_off" msgid="2230487165558877262">"WiFi је искључен."</string>
+ <string name="accessibility_quick_settings_wifi_changed_on" msgid="1490362586009027611">"WiFi је укључен."</string>
<string name="accessibility_quick_settings_mobile" msgid="1817825313718492906">"Мобилна мрежа: <xliff:g id="SIGNAL">%1$s</xliff:g>. <xliff:g id="TYPE">%2$s</xliff:g>. <xliff:g id="NETWORK">%3$s</xliff:g>."</string>
<string name="accessibility_quick_settings_battery" msgid="533594896310663853">"Батерија: <xliff:g id="STATE">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_airplane_off" msgid="1275658769368793228">"Режим рада у авиону је искључен."</string>
@@ -374,19 +374,19 @@
<string name="quick_settings_user_label" msgid="1253515509432672496">"Ја"</string>
<string name="quick_settings_user_title" msgid="8673045967216204537">"Корисник"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нови корисник"</string>
- <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
+ <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Веза није успостављена"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нема мреже"</string>
- <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi је искључен"</string>
- <string name="quick_settings_wifi_on_label" msgid="2489928193654318511">"Wi-Fi је укључен"</string>
- <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Није доступна ниједна Wi-Fi мрежа"</string>
+ <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi је искључен"</string>
+ <string name="quick_settings_wifi_on_label" msgid="2489928193654318511">"WiFi је укључен"</string>
+ <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Није доступна ниједна WiFi мрежа"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Укључује се..."</string>
<string name="quick_settings_cast_title" msgid="2279220930629235211">"Пребацивање екрана"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Пребацивање"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименовани уређај"</string>
<string name="quick_settings_cast_device_default_description" msgid="2580520859212250265">"Спремно за пребацивање"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Није доступан ниједан уређај"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi није повезан"</string>
+ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="2325362583903258677">"АУТОМАТСКА"</string>
<string name="quick_settings_inversion_label" msgid="5078769633069667698">"Обрни боје"</string>
@@ -642,8 +642,8 @@
<string name="output_none_found" msgid="5488087293120982770">"Није пронађен ниједан уређај"</string>
<string name="output_none_found_service_off" msgid="935667567681386368">"Није пронађен ниједан уређај. Пробајте да укључите услугу <xliff:g id="SERVICE">%1$s</xliff:g>"</string>
<string name="output_service_bt" msgid="4315362133973911687">"Bluetooth"</string>
- <string name="output_service_wifi" msgid="9003667810868222134">"Wi-Fi"</string>
- <string name="output_service_bt_wifi" msgid="7186882540475524124">"Bluetooth и Wi-Fi"</string>
+ <string name="output_service_wifi" msgid="9003667810868222134">"WiFi"</string>
+ <string name="output_service_bt_wifi" msgid="7186882540475524124">"Bluetooth и WiFi"</string>
<string name="system_ui_tuner" msgid="1471348823289954729">"Тјунер за кориснички интерфејс система"</string>
<string name="show_battery_percentage" msgid="6235377891802910455">"Приказуј уграђени проценат батерије"</string>
<string name="show_battery_percentage_summary" msgid="9053024758304102915">"Приказивање нивоа напуњености батерије у процентима унутар иконе на статусној траци када се батерија не пуни"</string>
@@ -946,7 +946,7 @@
<string name="mobile_data" msgid="4564407557775397216">"Мобилни подаци"</string>
<string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
- <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi је искључен"</string>
+ <string name="wifi_is_off" msgid="5389597396308001471">"WiFi је искључен"</string>
<string name="bt_is_off" msgid="7436344904889461591">"Bluetooth је искључен"</string>
<string name="dnd_is_off" msgid="3185706903793094463">"Режим Не узнемиравај је искључен"</string>
<string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"Аутоматско правило (<xliff:g id="ID_1">%s</xliff:g>) је укључило режим Не узнемиравај."</string>
@@ -958,7 +958,7 @@
<string name="running_foreground_services_title" msgid="5137313173431186685">"Апликације покренуте у позадини"</string>
<string name="running_foreground_services_msg" msgid="3009459259222695385">"Додирните за детаље о батерији и потрошњи података"</string>
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Желите да искључите мобилне податке?"</string>
- <string name="mobile_data_disable_message" msgid="8604966027899770415">"Нећете имати приступ подацима или интернету преко мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>. Интернет ће бити доступан само преко Wi-Fi везе."</string>
+ <string name="mobile_data_disable_message" msgid="8604966027899770415">"Нећете имати приступ подацима или интернету преко мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>. Интернет ће бити доступан само преко WiFi везе."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"мобилни оператер"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Подешавања не могу да верификују ваш одговор јер апликација скрива захтев за дозволу."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4b1ed0a..1ab776b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1218,6 +1218,8 @@
<dimen name="bubble_message_padding">4dp</dimen>
<!-- Offset between bubbles in their stacked position. -->
<dimen name="bubble_stack_offset">10dp</dimen>
+ <!-- Offset between stack y and animation y for bubble swap. -->
+ <dimen name="bubble_swap_animation_offset">15dp</dimen>
<!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
<dimen name="bubble_stack_offscreen">9dp</dimen>
<!-- How far down the screen the stack starts. -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
new file mode 100644
index 0000000..22ffd28
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.pip;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.SurfaceControl;
+
+/**
+ * TODO(b/171721389): unify this class with
+ * {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}, for instance, there should be one
+ * source of truth on enabling/disabling and the actual value of corner radius.
+ */
+public class PipSurfaceTransactionHelper {
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+ private final RectF mTmpSourceRectF = new RectF();
+ private final Rect mTmpDestinationRect = new Rect();
+
+ public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ mTmpSourceRectF.set(sourceBounds);
+ mTmpDestinationRect.set(sourceBounds);
+ mTmpDestinationRect.inset(insets);
+ // Scale by the shortest edge and offset such that the top/left of the scaled inset
+ // source rect aligns with the top/left of the destination bounds
+ final float scale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ final float left = destinationBounds.left - insets.left * scale;
+ final float top = destinationBounds.top - insets.top * scale;
+ mTmpTransform.setScale(scale, scale);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setWindowCrop(leash, mTmpDestinationRect)
+ .setPosition(leash, left, top);
+ }
+
+ public void reset(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds) {
+ resetScale(tx, leash, destinationBounds);
+ resetCornerRadius(tx, leash);
+ crop(tx, leash, destinationBounds);
+ }
+
+ public void resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ }
+
+ public void resetCornerRadius(SurfaceControl.Transaction tx, SurfaceControl leash) {
+ tx.setCornerRadius(leash, 0);
+ }
+
+ public void crop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java
new file mode 100644
index 0000000..0b1141e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.content.ClipDescription;
+import android.content.Intent;
+
+/**
+ * Wrapper around ClipDescription.
+ */
+public abstract class ClipDescriptionCompat {
+
+ public static String MIMETYPE_APPLICATION_ACTIVITY =
+ ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+
+ public static String MIMETYPE_APPLICATION_SHORTCUT =
+ ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+
+ public static String MIMETYPE_APPLICATION_TASK =
+ ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+ public static String EXTRA_PENDING_INTENT = ClipDescription.EXTRA_PENDING_INTENT;
+
+ public static String EXTRA_TASK_ID = Intent.EXTRA_TASK_ID;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java
new file mode 100644
index 0000000..d24c779
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.pm.LauncherApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * Wrapper around LauncherApps.
+ */
+public abstract class LauncherAppsCompat {
+
+ public static PendingIntent getMainActivityLaunchIntent(LauncherApps launcherApps,
+ ComponentName component, Bundle startActivityOptions, UserHandle user) {
+ return launcherApps.getMainActivityLaunchIntent(component, startActivityOptions, user);
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
index 326c2aa..7b9ebc0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
@@ -17,8 +17,11 @@
package com.android.systemui.shared.system;
import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
public class TaskInfoCompat {
@@ -34,6 +37,10 @@
return info.configuration.windowConfiguration.getWindowingMode();
}
+ public static Rect getWindowConfigurationBounds(TaskInfo info) {
+ return info.configuration.windowConfiguration.getBounds();
+ }
+
public static boolean supportsSplitScreenMultiWindow(TaskInfo info) {
return info.supportsSplitScreenMultiWindow;
}
@@ -45,4 +52,16 @@
public static ActivityManager.TaskDescription getTaskDescription(TaskInfo info) {
return info.taskDescription;
}
+
+ public static ActivityInfo getTopActivityInfo(TaskInfo info) {
+ return info.topActivityInfo;
+ }
+
+ public static boolean isAutoEnterPipEnabled(PictureInPictureParams params) {
+ return params.isAutoEnterEnabled();
+ }
+
+ public static Rect getPipSourceRectHint(PictureInPictureParams params) {
+ return params.getSourceRectHint();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 9f7358b..8bcffc8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -113,6 +113,18 @@
setClickable(true);
}
+ public void showDotAndBadge(boolean onLeft) {
+ removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
+ animateDotBadgePositions(onLeft);
+
+ }
+
+ public void hideDotAndBadge(boolean onLeft) {
+ addDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
+ mOnLeft = onLeft;
+ hideBadge();
+ }
+
/**
* Updates the view with provided info.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0714c5e..1201d42 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -90,6 +90,7 @@
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
@@ -1479,12 +1480,24 @@
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
}
+ /**
+ * Update bubble order and pointer position.
+ */
public void updateBubbleOrder(List<Bubble> bubbles) {
- for (int i = 0; i < bubbles.size(); i++) {
- Bubble bubble = bubbles.get(i);
- mBubbleContainer.reorderView(bubble.getIconView(), i);
+ final Runnable reorder = () -> {
+ for (int i = 0; i < bubbles.size(); i++) {
+ Bubble bubble = bubbles.get(i);
+ mBubbleContainer.reorderView(bubble.getIconView(), i);
+ }
+ };
+ if (mIsExpanded) {
+ reorder.run();
+ updateBubbleIcons();
+ } else {
+ List<View> bubbleViews = bubbles.stream()
+ .map(b -> b.getIconView()).collect(Collectors.toList());
+ mStackAnimationController.animateReorder(bubbleViews, reorder);
}
- updateBubbleIcons();
updatePointerPosition();
}
@@ -2595,17 +2608,11 @@
bv.setZ((mMaxBubbles * mBubbleElevation) - i);
if (mIsExpanded) {
- bv.removeDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.BEHIND_STACK);
- bv.animateDotBadgePositions(false /* onLeft */);
+ bv.showDotAndBadge(false /* onLeft */);
} else if (i == 0) {
- bv.removeDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.BEHIND_STACK);
- bv.animateDotBadgePositions(!mStackOnLeftOrWillBe);
+ bv.showDotAndBadge(!mStackOnLeftOrWillBe);
} else {
- bv.addDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.BEHIND_STACK);
- bv.hideBadge();
+ bv.hideDotAndBadge(!mStackOnLeftOrWillBe);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 31e1ca8..c410b82 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -34,6 +34,7 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
+import com.android.systemui.bubbles.BadgedImageView;
import com.android.systemui.bubbles.BubblePositioner;
import com.android.systemui.bubbles.BubbleStackView;
import com.android.wm.shell.animation.PhysicsAnimator;
@@ -45,6 +46,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.List;
import java.util.Set;
import java.util.function.IntSupplier;
@@ -63,6 +65,10 @@
private static final float ANIMATE_IN_STIFFNESS = 1000f;
private static final int ANIMATE_IN_START_DELAY = 25;
+ /** Values to use for animating updated bubble to top of stack. */
+ private static final float BUBBLE_SWAP_SCALE = 0.8f;
+ private static final long BUBBLE_SWAP_DURATION = 300L;
+
/**
* Values to use for the default {@link SpringForce} provided to the physics animation layout.
*/
@@ -180,6 +186,12 @@
/** Horizontal offset of bubbles in the stack. */
private float mStackOffset;
+ /** Offset between stack y and animation y for bubble swap. */
+ private float mSwapAnimationOffset;
+ /** Max number of bubbles to show in the expanded bubble row. */
+ private int mMaxBubbles;
+ /** Default bubble elevation. */
+ private int mElevation;
/** Diameter of the bubble icon. */
private int mBubbleBitmapSize;
/** Width of the bubble (icon and padding). */
@@ -770,6 +782,50 @@
}
}
+ public void animateReorder(List<View> bubbleViews, Runnable after) {
+ for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
+ View view = bubbleViews.get(newIndex);
+ final int oldIndex= mLayout.indexOfChild(view);
+ animateSwap(view, oldIndex, newIndex, after);
+ }
+ }
+
+ private void animateSwap(View view, int oldIndex, int newIndex, Runnable finishReorder) {
+ final float newY = getStackPosition().y + newIndex * mSwapAnimationOffset;
+ final float swapY = newIndex == 0
+ ? newY - mSwapAnimationOffset // Above top of stack
+ : newY + mSwapAnimationOffset; // Below where bubble will be
+ view.animate()
+ .scaleX(BUBBLE_SWAP_SCALE)
+ .scaleY(BUBBLE_SWAP_SCALE)
+ .translationY(swapY)
+ .setDuration(BUBBLE_SWAP_DURATION)
+ .withEndAction(() -> finishSwapAnimation(view, oldIndex, newIndex, finishReorder));
+ }
+
+ private void finishSwapAnimation(View view, int oldIndex, int newIndex,
+ Runnable finishReorder) {
+
+ // At this point, swapping bubbles have the least overlap.
+ // Update z-index and badge visibility here for least jarring transition.
+ view.setZ((mMaxBubbles * mElevation) - newIndex);
+ BadgedImageView bv = (BadgedImageView) view;
+ if (oldIndex == 0 && newIndex > 0) {
+ bv.hideDotAndBadge(!isStackOnLeftSide());
+ } else if (oldIndex > 0 && newIndex == 0) {
+ bv.showDotAndBadge(!isStackOnLeftSide());
+ }
+
+ // Animate bubble back into stack, at new index and original size.
+ final float newY = getStackPosition().y + newIndex * mStackOffset;
+ view.animate()
+ .scaleX(1f)
+ .scaleY(1f)
+ .translationY(newY)
+ .setDuration(BUBBLE_SWAP_DURATION)
+ .withEndAction(() -> finishReorder.run());
+ }
+
@Override
void onChildReordered(View child, int oldIndex, int newIndex) {
if (isStackPositionSet()) {
@@ -781,6 +837,9 @@
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
Resources res = layout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+ mSwapAnimationOffset = res.getDimensionPixelSize(R.dimen.bubble_swap_animation_offset);
+ mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
+ mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index e3ee2a1..fff185b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,18 @@
return buffer;
}
+ /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
+ @Provides
+ @SysUISingleton
+ @PrivacyLog
+ public static LogBuffer providePrivacyLogBuffer(
+ LogcatEchoTracker bufferFilter,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
new file mode 100644
index 0000000..e96e532
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for privacy indicator-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface PrivacyLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 2ddcc5a..1a9dd71 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -69,16 +69,29 @@
/** Converts {@code drawable} to a {@link Bitmap}. */
public static Bitmap convertDrawableToBitmap(Drawable drawable) {
- if (drawable instanceof BitmapDrawable) {
- return ((BitmapDrawable) drawable).getBitmap();
+ if (drawable == null) {
+ return null;
}
- // We use max below because the drawable might have no intrinsic width/height (e.g. if the
- // drawable is a solid color).
- Bitmap bitmap =
- Bitmap.createBitmap(
- Math.max(drawable.getIntrinsicWidth(), 1),
- Math.max(drawable.getIntrinsicHeight(), 1),
- Bitmap.Config.ARGB_8888);
+
+ if (drawable instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (bitmapDrawable.getBitmap() != null) {
+ return bitmapDrawable.getBitmap();
+ }
+ }
+
+ Bitmap bitmap;
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ // Single color bitmap will be created of 1x1 pixel
+ } else {
+ bitmap = Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888
+ );
+ }
+
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
new file mode 100644
index 0000000..44f173b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.widget;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.systemui.people.PeopleSpaceUtils;
+
+/** Proxy activity to launch ShortcutInfo's conversation. */
+public class LaunchConversationActivity extends Activity {
+ private static final String TAG = "PeopleSpaceLaunchConv";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate called");
+
+ Intent intent = getIntent();
+ ShortcutInfo shortcutInfo = (ShortcutInfo) intent.getParcelableExtra(
+ PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO
+ );
+ if (shortcutInfo != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Launching conversation with shortcutInfo id " + shortcutInfo.getId());
+ }
+ try {
+ LauncherApps launcherApps =
+ getApplicationContext().getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(
+ shortcutInfo, null, null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception starting shortcut:" + e);
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo.");
+ }
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 85801f9..aa98b61 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,6 +16,7 @@
package com.android.systemui.people.widget;
+import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
@@ -31,6 +32,8 @@
private static final String TAG = "PeopleSpaceWidgetPvd";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+ public static final String EXTRA_SHORTCUT_INFO = "extra_shortcut_info";
+
/** Called when widget updates. */
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
@@ -45,6 +48,19 @@
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
views.setRemoteAdapter(R.id.widget_list_view, intent);
+ Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
+ activityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NO_HISTORY
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ appWidgetId,
+ activityIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
+ views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent);
+
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list_view);
appWidgetManager.updateAppWidget(appWidgetId, views);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
index 093925a..c68c306 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
@@ -130,6 +130,10 @@
mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0)
)
);
+
+ Intent fillInIntent = new Intent();
+ fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO, shortcutInfo);
+ personView.setOnClickFillInIntent(R.id.item, fillInIntent);
} catch (Exception e) {
Log.e(TAG, "Couldn't retrieve shortcut information", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 3da1363..7359e79 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -19,20 +19,31 @@
typealias Privacy = PrivacyType
-enum class PrivacyType(val nameId: Int, val iconId: Int) {
+enum class PrivacyType(val nameId: Int, val iconId: Int, val logName: String) {
// This is uses the icons used by the corresponding permission groups in the AndroidManifest
- TYPE_CAMERA(R.string.privacy_type_camera,
- com.android.internal.R.drawable.perm_group_camera),
- TYPE_MICROPHONE(R.string.privacy_type_microphone,
- com.android.internal.R.drawable.perm_group_microphone),
- TYPE_LOCATION(R.string.privacy_type_location,
- com.android.internal.R.drawable.perm_group_location);
+ TYPE_CAMERA(
+ R.string.privacy_type_camera,
+ com.android.internal.R.drawable.perm_group_camera,
+ "camera"
+ ),
+ TYPE_MICROPHONE(
+ R.string.privacy_type_microphone,
+ com.android.internal.R.drawable.perm_group_microphone,
+ "microphone"
+ ),
+ TYPE_LOCATION(
+ R.string.privacy_type_location,
+ com.android.internal.R.drawable.perm_group_location,
+ "location"
+ );
fun getName(context: Context) = context.resources.getString(nameId)
fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
}
-data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication)
+data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) {
+ fun toLog(): String = "(${privacyType.logName}, ${application.packageName}(${application.uid}))"
+}
data class PrivacyApplication(val packageName: String, val uid: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index dc5ba69..87ffbd4 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -48,6 +49,7 @@
@Background private val bgExecutor: Executor,
private val deviceConfigProxy: DeviceConfigProxy,
private val userTracker: UserTracker,
+ private val logger: PrivacyLogger,
dumpManager: DumpManager
) : Dumpable {
@@ -158,6 +160,7 @@
}
val userId = UserHandle.getUserId(uid)
if (userId in currentUserIds) {
+ logger.logUpdatedItemFromAppOps(code, uid, packageName, active)
update(false)
}
}
@@ -194,6 +197,7 @@
bgExecutor.execute {
if (updateUsers) {
currentUserIds = userTracker.userProfiles.map { it.id }
+ logger.logCurrentProfilesChanged(currentUserIds)
}
updateListAndNotifyChanges.run()
}
@@ -260,6 +264,8 @@
}
val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
.mapNotNull { toPrivacyItem(it) }.distinct()
+ logger.logUpdatedPrivacyItemsList(
+ list.joinToString(separator = ", ", transform = PrivacyItem::toLog))
privacyList = list
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
new file mode 100644
index 0000000..c88676e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.PrivacyLog
+import javax.inject.Inject
+
+private const val TAG = "PrivacyLog"
+
+class PrivacyLogger @Inject constructor(
+ @PrivacyLog private val buffer: LogBuffer
+) {
+
+ fun logUpdatedItemFromAppOps(code: Int, uid: Int, packageName: String, active: Boolean) {
+ log(LogLevel.INFO, {
+ int1 = code
+ int2 = uid
+ str1 = packageName
+ bool1 = active
+ }, {
+ "App Op: $int1 for $str1($int2), active=$bool1"
+ })
+ }
+
+ fun logUpdatedPrivacyItemsList(listAsString: String) {
+ log(LogLevel.INFO, {
+ str1 = listAsString
+ }, {
+ "Updated list: $str1"
+ })
+ }
+
+ fun logCurrentProfilesChanged(profiles: List<Int>) {
+ log(LogLevel.INFO, {
+ str1 = profiles.toString()
+ }, {
+ "Profiles changed: $str1"
+ })
+ }
+
+ fun logChipVisible(visible: Boolean) {
+ log(LogLevel.INFO, {
+ bool1 = visible
+ }, {
+ "Chip visible: $bool1"
+ })
+ }
+
+ fun logStatusBarIconsVisible(
+ showCamera: Boolean,
+ showMichrophone: Boolean,
+ showLocation: Boolean
+ ) {
+ log(LogLevel.INFO, {
+ bool1 = showCamera
+ bool2 = showMichrophone
+ bool3 = showLocation
+ }, {
+ "Status bar icons visible: camera=$bool1, microphone=$bool2, location=$bool3"
+ })
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7e2433a..8e0e4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -205,12 +205,11 @@
mQSPanelContainer.setLayoutParams(layoutParams);
mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
- mContentPaddingStart = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_start);
- int newPaddingEnd = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- boolean marginsChanged = newPaddingEnd != mContentPaddingEnd;
- mContentPaddingEnd = newPaddingEnd;
+ int padding = getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_shade_content_margin_horizontal);
+ boolean marginsChanged = padding != mContentPaddingStart || padding != mContentPaddingEnd;
+ mContentPaddingStart = padding;
+ mContentPaddingEnd = padding;
if (marginsChanged) {
updatePaddingsAndMargins();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 44f847c..32904a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -43,6 +43,7 @@
import com.android.systemui.privacy.PrivacyChipEvent;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
@@ -90,6 +91,7 @@
private final StatusIconContainer mIconContainer;
private final StatusBarIconController.TintedIconManager mIconManager;
private final DemoMode mDemoModeReceiver;
+ private final PrivacyLogger mPrivacyLogger;
private boolean mListening;
private AlarmClockInfo mNextAlarm;
@@ -213,7 +215,8 @@
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
CommandQueue commandQueue, DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
- QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
+ PrivacyLogger privacyLogger) {
super(view);
mZenModeController = zenModeController;
mNextAlarmController = nextAlarmController;
@@ -228,6 +231,7 @@
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
mHeaderQsPanelController = quickQSPanelController;
+ mPrivacyLogger = privacyLogger;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
@@ -323,6 +327,7 @@
private void setChipVisibility(boolean chipVisible) {
if (chipVisible && getChipEnabled()) {
mPrivacyChip.setVisibility(View.VISIBLE);
+ mPrivacyLogger.logChipVisible(true);
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
if (!mPrivacyChipLogged && mListening) {
@@ -330,6 +335,7 @@
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
}
} else {
+ mPrivacyLogger.logChipVisible(false);
mPrivacyChip.setVisibility(View.GONE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index c0061ad..b2ebf3f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -281,8 +281,10 @@
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// cancel current pending intent (if any) since clipData isn't used for matching
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
- sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ context, 0, sharingChooserIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null, UserHandle.CURRENT);
// Create a share action for the notification
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
@@ -294,7 +296,8 @@
mSmartActionsEnabled)
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
@@ -323,7 +326,7 @@
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
- editIntent, 0, null, UserHandle.CURRENT);
+ editIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
@@ -338,7 +341,8 @@
mSmartActionsEnabled)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
@@ -360,7 +364,9 @@
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_CANCEL_CURRENT
+ | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE);
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
r.getString(com.android.internal.R.string.delete), deleteAction);
@@ -401,7 +407,7 @@
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
broadcastIntent).setContextual(true).addExtras(extras).build());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 5976582..25c8e7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -170,7 +170,7 @@
private void sanitizeChild(View child) {
if (child != null) {
ViewGroup header = child.findViewById(
- com.android.internal.R.id.notification_header);
+ com.android.internal.R.id.notification_top_line);
sanitizeHeader(header);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 160b6f7..71e1d12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -336,7 +336,6 @@
? contractedHeader.getPaddingLeft()
: paddingEnd,
contractedHeader.getPaddingBottom());
- contractedHeader.setShowWorkBadgeAtEnd(false);
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 0fc4c42..5aeacab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -22,8 +22,10 @@
import android.content.Context;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
+import android.view.NotificationTopLineView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
@@ -44,7 +46,7 @@
import java.util.Stack;
/**
- * Wraps a notification header view.
+ * Wraps a notification view which may or may not include a header.
*/
public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
@@ -56,6 +58,7 @@
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
protected NotificationHeaderView mNotificationHeader;
+ protected NotificationTopLineView mNotificationTopLine;
private TextView mHeaderText;
private TextView mAppNameText;
private ImageView mWorkProfileImage;
@@ -107,14 +110,15 @@
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
+ mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line);
mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
}
private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
View.OnClickListener listener = row.getFeedbackOnClickListener();
- if (mNotificationHeader != null) {
- mNotificationHeader.setFeedbackOnClickListener(listener);
+ if (mNotificationTopLine != null) {
+ mNotificationTopLine.setFeedbackOnClickListener(listener);
}
if (mFeedbackIcon != null) {
mFeedbackIcon.setOnClickListener(listener);
@@ -158,13 +162,11 @@
mAppNameText.setTextAppearance(
com.android.internal.R.style
.TextAppearance_DeviceDefault_Notification_Conversation_AppName);
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
layoutParams.setMarginStart(0);
}
if (mIconContainer != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width =
mIconContainer.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.conversation_content_start);
@@ -174,8 +176,7 @@
layoutParams.setMarginStart(marginStart * -1);
}
if (mIcon != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams();
layoutParams.setMarginEnd(0);
}
}
@@ -187,21 +188,18 @@
com.android.internal.R.attr.notificationHeaderTextAppearance,
com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
mAppNameText.setTextAppearance(textAppearance);
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_app_name_margin_start);
layoutParams.setMarginStart(marginStart);
}
if (mIconContainer != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.setMarginStart(0);
}
if (mIcon != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams();
final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_icon_margin_end);
layoutParams.setMarginEnd(marginEnd);
@@ -261,6 +259,7 @@
@Override
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 6ed092f..d537241 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -50,6 +50,7 @@
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyType;
+import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
@@ -145,6 +146,7 @@
private final SensorPrivacyController mSensorPrivacyController;
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
+ private final PrivacyLogger mPrivacyLogger;
private boolean mZenVisible;
private boolean mVolumeVisible;
@@ -172,7 +174,8 @@
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker,
- PrivacyItemController privacyItemController) {
+ PrivacyItemController privacyItemController,
+ PrivacyLogger privacyLogger) {
mIconController = iconController;
mCommandQueue = commandQueue;
mBroadcastDispatcher = broadcastDispatcher;
@@ -197,6 +200,7 @@
mUiBgExecutor = uiBgExecutor;
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
+ mPrivacyLogger = privacyLogger;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -675,6 +679,7 @@
|| mPrivacyItemController.getLocationAvailable()) {
mIconController.setIconVisibility(mSlotLocation, showLocation);
}
+ mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index c84589a..4552026 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,9 +73,7 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -315,7 +313,8 @@
mRemoteInputs = remoteInputs;
mRemoteInput = remoteInput;
mEditText.setHint(mRemoteInput.getLabel());
- mEditText.mSupportedMimeTypes = remoteInput.getAllowedDataTypes();
+ mEditText.mSupportedMimeTypes = (remoteInput.getAllowedDataTypes() == null) ? null
+ : remoteInput.getAllowedDataTypes().toArray(new String[0]);
mEntry.editedSuggestionInfo = editedSuggestionInfo;
if (editedSuggestionInfo != null) {
@@ -574,7 +573,7 @@
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
UserHandle mUser;
- private Set<String> mSupportedMimeTypes;
+ private String[] mSupportedMimeTypes;
public RemoteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -585,35 +584,31 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- setOnReceiveContentCallback(new OnReceiveContentCallback<View>() {
- @Override
- public boolean onReceiveContent(@NonNull View view, @NonNull Payload payload) {
- ClipData clip = payload.getClip();
- if (clip.getItemCount() == 0) {
- return false;
- }
- Uri contentUri = clip.getItemAt(0).getUri();
- ClipDescription description = clip.getDescription();
- String mimeType = null;
- if (description.getMimeTypeCount() > 0) {
- mimeType = description.getMimeType(0);
- }
- if (mimeType != null) {
- Intent dataIntent = mRemoteInputView
- .prepareRemoteInputFromData(mimeType, contentUri);
- mRemoteInputView.sendRemoteInput(dataIntent);
- }
- return true;
- }
-
- @NonNull
- @Override
- public Set<String> getSupportedMimeTypes(@NonNull View view) {
- return mSupportedMimeTypes != null
- ? mSupportedMimeTypes
- : Collections.emptySet();
- }
- });
+ if (mSupportedMimeTypes != null && mSupportedMimeTypes.length > 0) {
+ setOnReceiveContentCallback(mSupportedMimeTypes,
+ new OnReceiveContentCallback<View>() {
+ @Override
+ public boolean onReceiveContent(@NonNull View view,
+ @NonNull Payload payload) {
+ ClipData clip = payload.getClip();
+ if (clip.getItemCount() == 0) {
+ return false;
+ }
+ Uri contentUri = clip.getItemAt(0).getUri();
+ ClipDescription description = clip.getDescription();
+ String mimeType = null;
+ if (description.getMimeTypeCount() > 0) {
+ mimeType = description.getMimeType(0);
+ }
+ if (mimeType != null) {
+ Intent dataIntent = mRemoteInputView
+ .prepareRemoteInputFromData(mimeType, contentUri);
+ mRemoteInputView.sendRemoteInput(dataIntent);
+ }
+ return true;
+ }
+ });
+ }
}
private void defocusIfNeeded(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 1c682e3..409d136 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -129,6 +129,8 @@
mCallback = callback;
mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
packageName);
+ // Set as trusted overlay so touches can pass through toasts
+ mPresenter.getLayoutParams().setTrustedOverlay();
mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ead2fc1..cff1c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -102,8 +102,9 @@
@WMSingleton
@Provides
- static DragAndDropController provideDragAndDropController(DisplayController displayController) {
- return new DragAndDropController(displayController);
+ static DragAndDropController provideDragAndDropController(Context context,
+ DisplayController displayController) {
+ return new DragAndDropController(context, displayController);
}
@WMSingleton
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
index cd94f84..c401fab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
@@ -65,6 +66,8 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var logger: PrivacyLogger
private lateinit var privacyItemController: PrivacyItemController
private lateinit var executor: FakeExecutor
@@ -77,8 +80,8 @@
executor,
deviceConfigProxy,
userTracker,
- dumpManager
- )
+ logger,
+ dumpManager)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 16a1105..3e83498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -29,10 +29,12 @@
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.time.FakeSystemClock
import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.not
@@ -86,6 +88,8 @@
private lateinit var userTracker: UserTracker
@Mock
private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var logger: PrivacyLogger
@Captor
private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
@Captor
@@ -102,8 +106,8 @@
executor,
deviceConfigProxy,
userTracker,
- dumpManager
- )
+ logger,
+ dumpManager)
}
@Before
@@ -300,6 +304,45 @@
verify(callback, never()).onPrivacyItemsChanged(any())
}
+ @Test
+ fun testLogActiveChanged() {
+ privacyItemController.addCallback(callback)
+ executor.runAllReady()
+
+ verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+ argCaptorCallback.value.onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+
+ verify(logger).logUpdatedItemFromAppOps(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+ }
+
+ @Test
+ fun testLogListUpdated() {
+ doReturn(listOf(
+ AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0))
+ ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+ privacyItemController.addCallback(callback)
+ executor.runAllReady()
+
+ verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+ argCaptorCallback.value.onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+ executor.runAllReady()
+
+ val expected = PrivacyItem(
+ PrivacyType.TYPE_LOCATION,
+ PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID)
+ )
+
+ val captor = argumentCaptor<String>()
+ verify(logger, atLeastOnce()).logUpdatedPrivacyItemsList(capture(captor))
+ // Let's look at the last log
+ val values = captor.allValues
+ assertTrue(values[values.size - 1].contains(expected.toLog()))
+ }
+
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 2269951..26b5d26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
@@ -86,6 +87,8 @@
@Mock
private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock
+ private lateinit var privacyLogger: PrivacyLogger
+ @Mock
private lateinit var iconContainer: StatusIconContainer
@Mock
private lateinit var qsCarrierGroup: QSCarrierGroup
@@ -123,7 +126,8 @@
demoModeController,
userTracker,
quickQSPanelController,
- qsCarrierGroupControllerBuilder
+ qsCarrierGroupControllerBuilder,
+ privacyLogger
)
}
diff --git a/packages/Tethering/tests/Android.bp b/packages/Tethering/tests/Android.bp
new file mode 100644
index 0000000..cb0a20b
--- /dev/null
+++ b/packages/Tethering/tests/Android.bp
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2020 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.
+//
+
+filegroup {
+ name: "TetheringTestsJarJarRules",
+ srcs: ["jarjar-rules.txt"],
+ visibility: [
+ "//frameworks/base/packages/Tethering/tests:__subpackages__",
+ ]
+}
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 02bab9b..5765c01 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -79,6 +79,7 @@
// For NetworkStackUtils included in NetworkStackBase
"libnetworkstackutilsjni",
],
+ jarjar_rules: ":TetheringTestsJarJarRules",
compile_multilib: "both",
manifest: "AndroidManifest_coverage.xml",
-}
\ No newline at end of file
+}
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/jarjar-rules.txt
similarity index 90%
rename from packages/Tethering/tests/unit/jarjar-rules.txt
rename to packages/Tethering/tests/jarjar-rules.txt
index 7ed8963..c99ff7f 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/jarjar-rules.txt
@@ -10,7 +10,10 @@
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
+
# TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains.
# TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils.
zap android.os.test.TestLooperTest*
-zap com.android.test.filters.SelectTestTests*
\ No newline at end of file
+zap com.android.test.filters.SelectTestTests*
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 0413714..ef556cf 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -68,7 +68,6 @@
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
],
- jarjar_rules: "jarjar-rules.txt",
}
// Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -89,6 +88,7 @@
"device-tests",
"mts",
],
+ jarjar_rules: ":TetheringTestsJarJarRules",
defaults: ["TetheringTestsDefaults"],
compile_multilib: "both",
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 4c8925b..70cf045 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1122,4 +1122,8 @@
public abstract IncrementalStatesInfo getIncrementalStatesInfo(String packageName,
int filterCallingUid, int userId);
+ /**
+ * Notifies that a package has crashed or ANR'd.
+ */
+ public abstract void notifyPackageCrashOrAnr(String packageName);
}
diff --git a/services/core/java/android/content/pm/TestUtilityService.java b/services/core/java/android/content/pm/TestUtilityService.java
new file mode 100644
index 0000000..426352b
--- /dev/null
+++ b/services/core/java/android/content/pm/TestUtilityService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.IBinder;
+
+/**
+ * Utility methods for testing and debugging.
+ */
+public interface TestUtilityService {
+ /**
+ * Verifies validity of the token passed as a parameter to holdLock(). Throws an exception if
+ * the token is invalid.
+ */
+ void verifyHoldLockToken(IBinder token);
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index afea976..85d77f2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3998,13 +3998,11 @@
settingsPkgName + ".wifi.WifiNoInternetDialog");
}
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- mContext,
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
0 /* requestCode */,
intent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- null /* options */,
- UserHandle.CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, highPriority);
}
diff --git a/services/core/java/com/android/server/Dumpable.java b/services/core/java/com/android/server/Dumpable.java
new file mode 100644
index 0000000..d2bd66f
--- /dev/null
+++ b/services/core/java/com/android/server/Dumpable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+/**
+ * Interface used to dump {@link SystemServer} state that is not associated with any service.
+ */
+public interface Dumpable {
+
+ /**
+ * Dumps the state.
+ */
+ void dump(@NonNull IndentingPrintWriter pw, @Nullable String[] args);
+
+ /**
+ * Gets the name of the dumpable.
+ *
+ * <p>If not overridden, will return the simple class name.
+ */
+ default String getDumpableName() {
+ return Dumpable.this.getClass().getSimpleName();
+ }
+}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 20f68da..d4e912b6 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -75,9 +75,9 @@
@VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500;
/**
- * Number of taps required to launch panic ui.
+ * Number of taps required to launch emergency gesture ui.
*/
- private static final int PANIC_POWER_TAP_COUNT_THRESHOLD = 5;
+ private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5;
/**
* Number of taps required to launch camera shortcut.
@@ -138,9 +138,9 @@
private boolean mCameraDoubleTapPowerEnabled;
/**
- * Whether panic button gesture is currently enabled
+ * Whether emergency gesture is currently enabled
*/
- private boolean mPanicButtonGestureEnabled;
+ private boolean mEmergencyGestureEnabled;
private long mLastPowerDown;
private int mPowerButtonConsecutiveTaps;
@@ -178,7 +178,7 @@
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- updatePanicButtonGestureEnabled();
+ updateEmergencyGestureEnabled();
mUserId = ActivityManager.getCurrentUser();
mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
@@ -197,7 +197,7 @@
Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.PANIC_GESTURE_ENABLED),
+ Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED),
false, mSettingObserver, mUserId);
}
@@ -225,10 +225,10 @@
}
@VisibleForTesting
- void updatePanicButtonGestureEnabled() {
- boolean enabled = isPanicButtonGestureEnabled(mContext, mUserId);
+ void updateEmergencyGestureEnabled() {
+ boolean enabled = isEmergencyGestureEnabled(mContext, mUserId);
synchronized (this) {
- mPanicButtonGestureEnabled = enabled;
+ mEmergencyGestureEnabled = enabled;
}
}
@@ -357,11 +357,11 @@
}
/**
- * Whether to enable panic button gesture.
+ * Whether to enable emergency gesture.
*/
- public static boolean isPanicButtonGestureEnabled(Context context, int userId) {
+ public static boolean isEmergencyGestureEnabled(Context context, int userId) {
return Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.PANIC_GESTURE_ENABLED, 0, userId) != 0;
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED, 0, userId) != 0;
}
/**
@@ -409,7 +409,7 @@
return false;
}
boolean launchCamera = false;
- boolean launchPanic = false;
+ boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
synchronized (this) {
@@ -428,15 +428,15 @@
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
}
- // Check if we need to launch camera or panic flows
- if (mPanicButtonGestureEnabled) {
+ // Check if we need to launch camera or emergency gesture flows
+ if (mEmergencyGestureEnabled) {
// Commit to intercepting the powerkey event after the second "quick" tap to avoid
- // lockscreen changes between launching camera and the panic flow.
+ // lockscreen changes between launching camera and the emergency gesture flow.
if (mPowerButtonConsecutiveTaps > 1) {
intercept = interactive;
}
- if (mPowerButtonConsecutiveTaps == PANIC_POWER_TAP_COUNT_THRESHOLD) {
- launchPanic = true;
+ if (mPowerButtonConsecutiveTaps == EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD) {
+ launchEmergencyGesture = true;
}
}
if (mCameraDoubleTapPowerEnabled
@@ -461,18 +461,18 @@
mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
(int) powerTapInterval);
}
- } else if (launchPanic) {
- Slog.i(TAG, "Panic gesture detected, launching panic.");
- launchPanic = handlePanicButtonGesture();
+ } else if (launchEmergencyGesture) {
+ Slog.i(TAG, "Emergency gesture detected, launching.");
+ launchEmergencyGesture = handleEmergencyGesture();
// TODO(b/160006048): Add logging
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
- outLaunched.value = launchCamera || launchPanic;
- // Intercept power key event if the press is part of a gesture (camera, panic) and the user
- // has completed setup.
+ outLaunched.value = launchCamera || launchEmergencyGesture;
+ // Intercept power key event if the press is part of a gesture (camera, eGesture) and the
+ // user has completed setup.
return intercept && isUserSetupComplete();
}
@@ -512,27 +512,25 @@
}
/**
- * @return true if panic ui was launched, false otherwise.
+ * @return true if emergency gesture UI was launched, false otherwise.
*/
@VisibleForTesting
- boolean handlePanicButtonGesture() {
- // TODO(b/160006048): This is the wrong way to launch panic ui. Rewrite this to go
- // through SysUI
+ boolean handleEmergencyGesture() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- "GestureLauncher:handlePanicButtonGesture");
+ "GestureLauncher:handleEmergencyGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, String.format(
- "userSetupComplete = %s, ignoring panic gesture.",
+ "userSetupComplete = %s, ignoring emergency gesture.",
userSetupComplete));
}
return false;
}
if (DBG) {
Slog.d(TAG, String.format(
- "userSetupComplete = %s, performing panic gesture.",
+ "userSetupComplete = %s, performing emergency gesture.",
userSetupComplete));
}
StatusBarManagerInternal service = LocalServices.getService(
@@ -558,7 +556,7 @@
registerContentObservers();
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- updatePanicButtonGestureEnabled();
+ updateEmergencyGestureEnabled();
}
}
};
@@ -568,7 +566,7 @@
if (userId == mUserId) {
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- updatePanicButtonGestureEnabled();
+ updateEmergencyGestureEnabled();
}
}
};
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index c061137..c23f1ca 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.os.Build;
import android.os.Process;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -44,7 +45,7 @@
*
* @hide
*/
-public class SystemServerInitThreadPool {
+public final class SystemServerInitThreadPool implements Dumpable {
private static final String TAG = SystemServerInitThreadPool.class.getSimpleName();
private static final int SHUTDOWN_TIMEOUT_MILLIS = 20000;
private static final boolean IS_DEBUGGABLE = Build.IS_DEBUGGABLE;
@@ -53,6 +54,7 @@
@GuardedBy("LOCK")
private static SystemServerInitThreadPool sInstance;
+ private final int mSize; // used by dump() only
private final ExecutorService mService;
@GuardedBy("mPendingTasks")
@@ -62,9 +64,9 @@
private boolean mShutDown;
private SystemServerInitThreadPool() {
- final int size = Runtime.getRuntime().availableProcessors();
- Slog.i(TAG, "Creating instance with " + size + " threads");
- mService = ConcurrentUtils.newFixedThreadPool(size,
+ mSize = Runtime.getRuntime().availableProcessors();
+ Slog.i(TAG, "Creating instance with " + mSize + " threads");
+ mService = ConcurrentUtils.newFixedThreadPool(mSize,
"system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
}
@@ -123,11 +125,13 @@
*
* @throws IllegalStateException if it has been started already without being shut down yet.
*/
- static void start() {
+ static SystemServerInitThreadPool start() {
+ SystemServerInitThreadPool instance;
synchronized (LOCK) {
Preconditions.checkState(sInstance == null, TAG + " already started");
- sInstance = new SystemServerInitThreadPool();
+ instance = sInstance = new SystemServerInitThreadPool();
}
+ return instance;
}
/**
@@ -190,4 +194,22 @@
ActivityManagerService.dumpStackTraces(pids, null, null,
Watchdog.getInterestingNativePids(), null);
}
+
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ synchronized (LOCK) {
+ pw.printf("has instance: %b\n", (sInstance != null));
+ }
+ pw.printf("number of threads: %d\n", mSize);
+ pw.printf("service: %s\n", mService);
+ synchronized (mPendingTasks) {
+ pw.printf("is shutdown: %b\n", mShutDown);
+ final int pendingTasks = mPendingTasks.size();
+ if (pendingTasks == 0) {
+ pw.println("no pending tasks");
+ } else {
+ pw.printf("%d pending tasks: %s\n", pendingTasks, mPendingTasks);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 84d01ec..6c81de6 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -200,21 +200,21 @@
/**
* @hide
*/
- public void dump(@NonNull StringBuilder builder) {
- builder.append(getUserIdentifier());
+ public void dump(@NonNull PrintWriter pw) {
+ pw.print(getUserIdentifier());
if (!isFull() && !isManagedProfile()) return;
- builder.append('(');
+ pw.print('(');
boolean addComma = false;
if (isFull()) {
- builder.append("full");
+ pw.print("full");
}
if (isManagedProfile()) {
- if (addComma) builder.append(',');
- builder.append("mp");
+ if (addComma) pw.print(',');
+ pw.print("mp");
}
- builder.append(')');
+ pw.print(')');
}
}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index ff2661b..71a1821 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -27,6 +27,7 @@
import android.os.UserManagerInternal;
import android.util.ArrayMap;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -49,7 +50,7 @@
*
* {@hide}
*/
-public final class SystemServiceManager {
+public final class SystemServiceManager implements Dumpable {
private static final String TAG = SystemServiceManager.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int SERVICE_CALL_WARN_TIME_MS = 50;
@@ -489,31 +490,39 @@
return sSystemDir;
}
- /**
- * Outputs the state of this manager to the System log.
- */
- public void dump() {
- StringBuilder builder = new StringBuilder();
- builder.append("Current phase: ").append(mCurrentPhase).append('\n');
- builder.append("Services:\n");
- final int startedLen = mServices.size();
- for (int i = 0; i < startedLen; i++) {
- final SystemService service = mServices.get(i);
- builder.append("\t")
- .append(service.getClass().getSimpleName())
- .append("\n");
- }
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ pw.printf("Current phase: %d\n", mCurrentPhase);
synchronized (mTargetUsers) {
- builder.append("Current user: ").append(mCurrentUser).append('\n');
- builder.append("Target users: ");
- final int targetUsersSize = mTargetUsers.size();
- for (int i = 0; i < targetUsersSize; i++) {
- mTargetUsers.valueAt(i).dump(builder);
- if (i != targetUsersSize - 1) builder.append(',');
+ if (mCurrentUser != null) {
+ pw.print("Current user: "); mCurrentUser.dump(pw); pw.println();
+ } else {
+ pw.println("Current user not set!");
}
- builder.append('\n');
- }
- Slog.e(TAG, builder.toString());
+ final int targetUsersSize = mTargetUsers.size();
+ if (targetUsersSize > 0) {
+ pw.printf("%d target users: ", targetUsersSize);
+ for (int i = 0; i < targetUsersSize; i++) {
+ mTargetUsers.valueAt(i).dump(pw);
+ if (i != targetUsersSize - 1) pw.print(", ");
+ }
+ pw.println();
+ } else {
+ pw.println("No target users");
+ }
+ }
+ final int startedLen = mServices.size();
+ if (startedLen > 0) {
+ pw.printf("%d started services:\n", startedLen);
+ pw.increaseIndent();
+ for (int i = 0; i < startedLen; i++) {
+ final SystemService service = mServices.get(i);
+ pw.println(service.getClass().getCanonicalName());
+ }
+ pw.decreaseIndent();
+ } else {
+ pw.println("No started services");
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 32d95f5..112814c69 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -208,6 +208,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.SELinuxUtil;
import android.content.pm.ServiceInfo;
+import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -339,6 +340,7 @@
import com.android.server.ThreadPriorityBooster;
import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
+import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
@@ -1311,6 +1313,7 @@
PackageManagerInternal mPackageManagerInt;
PermissionManagerServiceInternal mPermissionManagerInt;
+ private TestUtilityService mTestUtilityService;
/**
* Whether to force background check on all apps (for battery saver) or not.
@@ -5784,6 +5787,14 @@
return mPermissionManagerInt;
}
+ private TestUtilityService getTestUtilityServiceLocked() {
+ if (mTestUtilityService == null) {
+ mTestUtilityService =
+ LocalServices.getService(TestUtilityService.class);
+ }
+ return mTestUtilityService;
+ }
+
@Override
public void appNotResponding(final String reason) {
final int callingPid = Binder.getCallingPid();
@@ -7595,6 +7606,10 @@
eventType, r, processName, null, null, null, null, null, null, crashInfo);
mAppErrors.crashApplication(r, crashInfo);
+ // Notify package manager service to possibly update package state
+ if (r != null && r.info != null && r.info.packageName != null) {
+ mPackageManagerInt.notifyPackageCrashOrAnr(r.info.packageName);
+ }
}
public void handleApplicationStrictModeViolation(
@@ -8210,13 +8225,25 @@
}
@Override
- public int getMemoryTrimLevel() {
+ public @MemFactor int getMemoryTrimLevel() {
enforceNotIsolatedCaller("getMyMemoryState");
synchronized (this) {
return mAppProfiler.getLastMemoryLevelLocked();
}
}
+ void setMemFactorOverride(@MemFactor int level) {
+ synchronized (this) {
+ if (level == mAppProfiler.getLastMemoryLevelLocked()) {
+ return;
+ }
+
+ mAppProfiler.setMemFactorOverrideLocked(level);
+ // Kick off an oom adj update since we forced a mem factor update.
+ updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ }
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -17339,11 +17366,12 @@
/**
* Holds the AM lock for the specified amount of milliseconds.
* Intended for use by the tests that need to imitate lock contention.
- * Requires permission identity of the shell UID.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
*/
@Override
- public void holdLock(int durationMs) {
- enforceCallingPermission(Manifest.permission.INJECT_EVENTS, "holdLock");
+ public void holdLock(IBinder token, int durationMs) {
+ getTestUtilityServiceLocked().verifyHoldLockToken(token);
synchronized (this) {
SystemClock.sleep(durationMs);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 31e7106..e3c071f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -25,6 +25,12 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -92,6 +98,7 @@
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.util.HexDump;
import com.android.internal.util.MemInfoReader;
+import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.compat.PlatformCompat;
import java.io.BufferedReader;
@@ -309,6 +316,8 @@
return runCompat(pw);
case "refresh-settings-cache":
return runRefreshSettingsCache();
+ case "memory-factor":
+ return runMemoryFactor(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -3014,6 +3023,81 @@
return -1;
}
+ private int runSetMemoryFactor(PrintWriter pw) throws RemoteException {
+ final String levelArg = getNextArgRequired();
+ @MemFactor int level = ADJ_MEM_FACTOR_NOTHING;
+ switch (levelArg) {
+ case "NORMAL":
+ level = ADJ_MEM_FACTOR_NORMAL;
+ break;
+ case "MODERATE":
+ level = ADJ_MEM_FACTOR_MODERATE;
+ break;
+ case "LOW":
+ level = ADJ_MEM_FACTOR_LOW;
+ break;
+ case "CRITICAL":
+ level = ADJ_MEM_FACTOR_CRITICAL;
+ break;
+ default:
+ try {
+ level = Integer.parseInt(levelArg);
+ } catch (NumberFormatException e) {
+ }
+ if (level < ADJ_MEM_FACTOR_NORMAL || level > ADJ_MEM_FACTOR_CRITICAL) {
+ getErrPrintWriter().println("Error: Unknown level option: " + levelArg);
+ return -1;
+ }
+ }
+ mInternal.setMemFactorOverride(level);
+ return 0;
+ }
+
+ private int runShowMemoryFactor(PrintWriter pw) throws RemoteException {
+ final @MemFactor int level = mInternal.getMemoryTrimLevel();
+ switch (level) {
+ case ADJ_MEM_FACTOR_NOTHING:
+ pw.println("<UNKNOWN>");
+ break;
+ case ADJ_MEM_FACTOR_NORMAL:
+ pw.println("NORMAL");
+ break;
+ case ADJ_MEM_FACTOR_MODERATE:
+ pw.println("MODERATE");
+ break;
+ case ADJ_MEM_FACTOR_LOW:
+ pw.println("LOW");
+ break;
+ case ADJ_MEM_FACTOR_CRITICAL:
+ pw.println("CRITICAL");
+ break;
+ }
+ pw.flush();
+ return 0;
+ }
+
+ private int runResetMemoryFactor(PrintWriter pw) throws RemoteException {
+ mInternal.setMemFactorOverride(ADJ_MEM_FACTOR_NOTHING);
+ return 0;
+ }
+
+ private int runMemoryFactor(PrintWriter pw) throws RemoteException {
+ mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
+ "runMemoryFactor()");
+
+ final String op = getNextArgRequired();
+ switch (op) {
+ case "set":
+ return runSetMemoryFactor(pw);
+ case "show":
+ return runShowMemoryFactor(pw);
+ case "reset":
+ return runResetMemoryFactor(pw);
+ default:
+ getErrPrintWriter().println("Error: unknown command '" + op + "'");
+ return -1;
+ }
+ }
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
@@ -3334,6 +3418,13 @@
pw.println(" Removes all existing overrides for all changes for ");
pw.println(" <PACKAGE_NAME> (back to default behaviour).");
pw.println(" It kills <PACKAGE_NAME> (to allow the toggle to take effect).");
+ pw.println(" memory-factor [command] [...]: sub-commands for overriding memory pressure factor");
+ pw.println(" set <NORMAL|MODERATE|LOW|CRITICAL>");
+ pw.println(" Overrides memory pressure factor. May also supply a raw int level");
+ pw.println(" show");
+ pw.println(" Shows the existing memory pressure factor");
+ pw.println(" reset");
+ pw.println(" Removes existing override for memory pressure factor");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 31ffb35..5e59a35 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -20,11 +20,16 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import android.annotation.BroadcastBehavior;
@@ -72,6 +77,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
+import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.ProcessList.ProcStateMemTracker;
import com.android.server.utils.PriorityDump;
@@ -202,7 +208,10 @@
* processes are going away for other reasons.
*/
@GuardedBy("mService")
- private int mLastMemoryLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ private @MemFactor int mLastMemoryLevel = ADJ_MEM_FACTOR_NORMAL;
+
+ @GuardedBy("mService")
+ private @MemFactor int mMemFactorOverride = ADJ_MEM_FACTOR_NOTHING;
/**
* The last total number of process we have, to determine if changes actually look
@@ -851,7 +860,7 @@
@GuardedBy("mService")
boolean isLastMemoryLevelNormal() {
- return mLastMemoryLevel <= ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ return mLastMemoryLevel <= ADJ_MEM_FACTOR_NORMAL;
}
@GuardedBy("mService")
@@ -868,6 +877,11 @@
}
@GuardedBy("mService")
+ void setMemFactorOverrideLocked(@MemFactor int factor) {
+ mMemFactorOverride = factor;
+ }
+
+ @GuardedBy("mService")
boolean updateLowMemStateLocked(int numCached, int numEmpty, int numTrimming) {
final int numOfLru = mService.mProcessList.getLruSizeLocked();
final long now = SystemClock.uptimeMillis();
@@ -885,28 +899,32 @@
&& numEmpty <= mService.mConstants.CUR_TRIM_EMPTY_PROCESSES) {
final int numCachedAndEmpty = numCached + numEmpty;
if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
+ memFactor = ADJ_MEM_FACTOR_CRITICAL;
} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
+ memFactor = ADJ_MEM_FACTOR_LOW;
} else {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
+ memFactor = ADJ_MEM_FACTOR_MODERATE;
}
} else {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ memFactor = ADJ_MEM_FACTOR_NORMAL;
}
}
// We always allow the memory level to go up (better). We only allow it to go
// down if we are in a state where that is allowed, *and* the total number of processes
// has gone down since last time.
if (DEBUG_OOM_ADJ) {
- Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor
+ Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + " override=" + mMemFactorOverride
+ " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel
+ " numProcs=" + mService.mProcessList.getLruSizeLocked()
+ " last=" + mLastNumProcesses);
}
+ boolean override;
+ if (override = (mMemFactorOverride != ADJ_MEM_FACTOR_NOTHING)) {
+ memFactor = mMemFactorOverride;
+ }
if (memFactor > mLastMemoryLevel) {
- if (!mAllowLowerMemLevel
- || mService.mProcessList.getLruSizeLocked() >= mLastNumProcesses) {
+ if (!override && (!mAllowLowerMemLevel
+ || mService.mProcessList.getLruSizeLocked() >= mLastNumProcesses)) {
memFactor = mLastMemoryLevel;
if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!");
}
@@ -924,17 +942,17 @@
mService.mAtmInternal == null || !mService.mAtmInternal.isSleeping(), now);
trackerMemFactor = mService.mProcessStats.getMemFactorLocked();
}
- if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
+ if (memFactor != ADJ_MEM_FACTOR_NORMAL) {
if (mLowRamStartTime == 0) {
mLowRamStartTime = now;
}
int step = 0;
int fgTrimLevel;
switch (memFactor) {
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ case ADJ_MEM_FACTOR_CRITICAL:
fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ case ADJ_MEM_FACTOR_LOW:
fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
break;
default:
@@ -947,7 +965,7 @@
if (mService.mAtmInternal.getPreviousProcess() != null) minFactor++;
if (factor < minFactor) factor = minFactor;
int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
- for (int i = numOfLru - 1; i >= 0; i--) {
+ for (int i = 0; i < numOfLru; i++) {
ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
if (allChanged || app.procStateChanged) {
mService.setProcessTrackerStateLocked(app, trackerMemFactor, now);
@@ -1032,7 +1050,7 @@
mLowRamTimeSinceLastIdle += now - mLowRamStartTime;
mLowRamStartTime = 0;
}
- for (int i = numOfLru - 1; i >= 0; i--) {
+ for (int i = 0; i < numOfLru; i++) {
ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
if (allChanged || app.procStateChanged) {
mService.setProcessTrackerStateLocked(app, trackerMemFactor, now);
@@ -1622,16 +1640,16 @@
@GuardedBy("mService")
void dumpLastMemoryLevelLocked(PrintWriter pw) {
switch (mLastMemoryLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_NORMAL:
+ case ADJ_MEM_FACTOR_NORMAL:
pw.println("normal)");
break;
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+ case ADJ_MEM_FACTOR_MODERATE:
pw.println("moderate)");
break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ case ADJ_MEM_FACTOR_LOW:
pw.println("low)");
break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ case ADJ_MEM_FACTOR_CRITICAL:
pw.println("critical)");
break;
default:
diff --git a/services/core/java/com/android/server/am/LowMemDetector.java b/services/core/java/com/android/server/am/LowMemDetector.java
index e82a207..8f79133 100644
--- a/services/core/java/com/android/server/am/LowMemDetector.java
+++ b/services/core/java/com/android/server/am/LowMemDetector.java
@@ -16,8 +16,19 @@
package com.android.server.am;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+import static com.android.internal.app.procstats.ProcessStats.ADJ_NOTHING;
+
+import android.annotation.IntDef;
+
import com.android.internal.annotations.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Detects low memory using PSI.
*
@@ -32,13 +43,20 @@
private final Object mPressureStateLock = new Object();
@GuardedBy("mPressureStateLock")
- private int mPressureState = MEM_PRESSURE_NONE;
+ private int mPressureState = ADJ_MEM_FACTOR_NORMAL;
+
+ public static final int ADJ_MEM_FACTOR_NOTHING = ADJ_NOTHING;
/* getPressureState return values */
- public static final int MEM_PRESSURE_NONE = 0;
- public static final int MEM_PRESSURE_LOW = 1;
- public static final int MEM_PRESSURE_MEDIUM = 2;
- public static final int MEM_PRESSURE_HIGH = 3;
+ @IntDef(prefix = { "ADJ_MEM_FACTOR_" }, value = {
+ ADJ_MEM_FACTOR_NOTHING,
+ ADJ_MEM_FACTOR_NORMAL,
+ ADJ_MEM_FACTOR_MODERATE,
+ ADJ_MEM_FACTOR_LOW,
+ ADJ_MEM_FACTOR_CRITICAL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MemFactor{}
LowMemDetector(ActivityManagerService am) {
mAm = am;
@@ -62,7 +80,7 @@
* there should be conversion performed here to translate pressure state
* into memFactor.
*/
- public int getMemFactor() {
+ public @MemFactor int getMemFactor() {
synchronized (mPressureStateLock) {
return mPressureState;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 53c6758..ccdd6a7 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1764,6 +1764,12 @@
makeAppNotRespondingLocked(activityShortComponentName,
annotation != null ? "ANR " + annotation : "ANR", info.toString());
+ // Notify package manager service to possibly update package state
+ if (aInfo != null && aInfo.packageName != null) {
+ mService.getPackageManagerInternalLocked().notifyPackageCrashOrAnr(
+ aInfo.packageName);
+ }
+
// mUiHandler can be null if the AMS is constructed with injector only. This will only
// happen in tests.
if (mService.mUiHandler != null) {
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
new file mode 100644
index 0000000..36acc3c
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsBiometricsTestCases"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 5dcadee..9ac12ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -52,11 +52,10 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.internal.R;
@@ -92,55 +91,6 @@
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull private List<ServiceProvider> mServiceProviders;
- @NonNull private final ArrayMap<Integer, TestSession> mTestSessions;
-
- private final class TestSession extends ITestSession.Stub {
- private final int mSensorId;
-
- TestSession(int sensorId) {
- mSensorId = sensorId;
- }
-
- @Override
- public void enableTestHal(boolean enableTestHal) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void startEnroll(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void finishEnroll(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void acceptAuthentication(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void rejectAuthentication(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void notifyAcquired(int userId, int acquireInfo) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void notifyError(int userId, int errorCode) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void cleanupInternalState(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
- }
/**
* Receives the incoming binder calls from FingerprintManager.
@@ -150,20 +100,22 @@
public ITestSession createTestSession(int sensorId, String opPackageName) {
Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- final TestSession session;
- synchronized (mTestSessions) {
- if (!mTestSessions.containsKey(sensorId)) {
- mTestSessions.put(sensorId, new TestSession(sensorId));
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider.createTestSession(sensorId, opPackageName);
}
- session = mTestSessions.get(sensorId);
}
- return session;
+
+ return null;
}
@Override // Binder call
public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ if (getContext().checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+ != PackageManager.PERMISSION_GRANTED) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
final List<FingerprintSensorPropertiesInternal> properties =
FingerprintService.this.getSensorProperties();
@@ -424,12 +376,28 @@
final long ident = Binder.clearCallingIdentity();
try {
- for (ServiceProvider provider : mServiceProviders) {
- for (FingerprintSensorPropertiesInternal props :
- provider.getSensorProperties()) {
- if (args.length > 0 && "--proto".equals(args[0])) {
- provider.dumpProto(props.sensorId, fd);
- } else {
+ if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props
+ : provider.getSensorProperties()) {
+ provider.dumpProtoState(props.sensorId, proto);
+ }
+ }
+ proto.flush();
+ } else if (args.length > 0 && "--proto".equals(args[0])) {
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props
+ : provider.getSensorProperties()) {
+ provider.dumpProtoMetrics(props.sensorId, fd);
+ }
+ }
+ } else {
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props
+ : provider.getSensorProperties()) {
+ pw.println("Dumping for sensorId: " + props.sensorId
+ + ", provider: " + provider.getClass().getSimpleName());
provider.dumpInternal(props.sensorId, pw);
}
}
@@ -622,7 +590,6 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mServiceProviders = new ArrayList<>();
- mTestSessions = new ArrayMap<>();
initializeAidlHals();
}
@@ -648,7 +615,7 @@
try {
final SensorProps[] props = fp.getSensorProps();
final FingerprintProvider provider =
- new FingerprintProvider(getContext(), props, fqName,
+ new FingerprintProvider(getContext(), props, instance,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index c2315fd..1ed66a2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.biometrics.ITestSession;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -110,7 +112,11 @@
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
- void dumpProto(int sensorId, @NonNull FileDescriptor fd);
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto);
+
+ void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+
+ @NonNull ITestSession createTestSession(int sensorId, @NonNull String opPackageName);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
new file mode 100644
index 0000000..6bb40e6
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static android.Manifest.permission.TEST_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * A test session implementation for {@link FingerprintProvider}. See
+ * {@link android.hardware.biometrics.BiometricTestSession}.
+ */
+class BiometricTestSessionImpl extends ITestSession.Stub {
+
+ private static final String TAG = "BiometricTestSessionImpl";
+
+ @NonNull private final Context mContext;
+ private final int mSensorId;
+ @NonNull private final FingerprintProvider mProvider;
+ @NonNull private final Sensor mSensor;
+ @NonNull private final Set<Integer> mEnrollmentIds;
+ @NonNull private final Random mRandom;
+
+ /**
+ * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
+ * test, since enrollment is a platform-only API. The authentication path is tested through
+ * the public FingerprintManager APIs and does not use this receiver.
+ */
+ private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
+ @Override
+ public void onEnrollResult(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(Fingerprint fp, int userId,
+ boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+
+ }
+
+ @Override
+ public void onError(int error, int vendorCode) {
+
+ }
+
+ @Override
+ public void onRemoved(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onChallengeGenerated(int sensorId, long challenge) {
+
+ }
+ };
+
+ BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+ @NonNull FingerprintProvider provider, @NonNull Sensor sensor) {
+ mContext = context;
+ mSensorId = sensorId;
+ mProvider = provider;
+ mSensor = sensor;
+ mEnrollmentIds = new HashSet<>();
+ mRandom = new Random();
+ }
+
+ @Override
+ public void setTestHalEnabled(boolean enabled) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mProvider.setTestHalEnabled(enabled);
+ mSensor.setTestHalEnabled(enabled);
+ }
+
+ @Override
+ public void startEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
+ mContext.getOpPackageName(), null /* surface */);
+ }
+
+ @Override
+ public void finishEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ int nextRandomId = mRandom.nextInt();
+ while (mEnrollmentIds.contains(nextRandomId)) {
+ nextRandomId = mRandom.nextInt();
+ }
+
+ mEnrollmentIds.add(nextRandomId);
+ mSensor.getSessionForUser(userId).mHalSessionCallback
+ .onEnrollmentProgress(nextRandomId, 0 /* remaining */);
+ }
+
+ @Override
+ public void acceptAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ // Fake authentication with any of the existing fingers
+ List<Fingerprint> fingerprints = FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId);
+ if (fingerprints.isEmpty()) {
+ Slog.w(TAG, "No fingerprints, returning");
+ return;
+ }
+ final int fid = fingerprints.get(0).getBiometricId();
+ mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid,
+ HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69]));
+ }
+
+ @Override
+ public void rejectAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
+ }
+
+ @Override
+ public void notifyAcquired(int userId, int acquireInfo) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mSensor.getSessionForUser(userId).mHalSessionCallback
+ .onAcquired((byte) acquireInfo, 0 /* vendorCode */);
+ }
+
+ @Override
+ public void notifyError(int userId, int errorCode) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode,
+ 0 /* vendorCode */);
+ }
+
+ @Override
+ public void cleanupInternalState(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mProvider.scheduleInternalCleanup(mSensorId, userId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index fec3cff..2ad1fa3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -47,6 +47,11 @@
// Nothing to do here
}
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
@Override
protected void startHalOperation() {
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index d713f98..4d07f58 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -24,6 +24,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
@@ -38,6 +39,7 @@
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.server.biometrics.Utils;
@@ -62,6 +64,8 @@
@SuppressWarnings("deprecation")
public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
+ private boolean mTestHalEnabled;
+
@NonNull private final Context mContext;
@NonNull private final String mHalInstanceName;
@NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -132,7 +136,7 @@
prop.commonProps.maxEnrollmentsPerUser,
prop.sensorType,
true /* resetLockoutRequiresHardwareAuthToken */);
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, mContext, mHandler,
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, gestureAvailabilityDispatcher);
mSensors.put(sensorId, sensor);
@@ -146,6 +150,13 @@
@Nullable
private synchronized IFingerprint getHalInstance() {
+ if (mTestHalEnabled) {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+
if (mDaemon != null) {
return mDaemon;
}
@@ -153,7 +164,8 @@
Slog.d(getTag(), "Daemon was null, reconnecting");
mDaemon = IFingerprint.Stub.asInterface(
- ServiceManager.waitForDeclaredService(mHalInstanceName));
+ ServiceManager.waitForDeclaredService(IFingerprint.DESCRIPTOR
+ + "/" + mHalInstanceName));
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -561,7 +573,14 @@
}
@Override
- public void dumpProto(int sensorId, @NonNull FileDescriptor fd) {
+ public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+ if (mSensors.contains(sensorId)) {
+ mSensors.get(sensorId).dumpProtoState(sensorId, proto);
+ }
+ }
+
+ @Override
+ public void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd) {
}
@@ -570,6 +589,12 @@
}
+ @NonNull
+ @Override
+ public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+ return mSensors.get(sensorId).createTestSession();
+ }
+
@Override
public void binderDied() {
Slog.e(getTag(), "HAL died");
@@ -582,4 +607,8 @@
}
});
}
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index d4ce896..51c30b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -19,7 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
@@ -31,11 +33,16 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.fingerprint.FingerprintServiceStateProto;
+import com.android.server.biometrics.fingerprint.SensorStateProto;
+import com.android.server.biometrics.fingerprint.UserStateProto;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -48,9 +55,7 @@
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -59,7 +64,11 @@
*/
@SuppressWarnings("deprecation")
class Sensor implements IBinder.DeathRecipient {
+
+ private boolean mTestHalEnabled;
+
@NonNull private final String mTag;
+ @NonNull private final FingerprintProvider mProvider;
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
@@ -91,33 +100,337 @@
});
}
- private static class Session {
+ static class Session {
@NonNull private final String mTag;
@NonNull private final ISession mSession;
private final int mUserId;
- private final ISessionCallback mSessionCallback;
+ @NonNull final HalSessionCallback mHalSessionCallback;
Session(@NonNull String tag, @NonNull ISession session, int userId,
- @NonNull ISessionCallback sessionCallback) {
+ @NonNull HalSessionCallback halSessionCallback) {
mTag = tag;
mSession = session;
mUserId = userId;
- mSessionCallback = sessionCallback;
+ mHalSessionCallback = halSessionCallback;
Slog.d(mTag, "New session created for user: " + userId);
}
}
- Sensor(@NonNull String tag, @NonNull Context context, @NonNull Handler handler,
- @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ static class HalSessionCallback extends ISessionCallback.Stub {
+
+ /**
+ * Interface to sends results to the HalSessionCallback's owner.
+ */
+ public interface Callback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final String mTag;
+ @NonNull private final BiometricScheduler mScheduler;
+ private final int mSensorId;
+ private final int mUserId;
+ @NonNull private final Callback mCallback;
+
+ HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull Callback callback) {
+ mContext = context;
+ mHandler = handler;
+ mTag = tag;
+ mScheduler = scheduler;
+ mSensorId = sensorId;
+ mUserId = userId;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStateChanged(int cookie, byte state) {
+ // TODO(b/162973174)
+ }
+
+ @Override
+ public void onChallengeGenerated(long challenge) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintGenerateChallengeClient)) {
+ Slog.e(mTag, "onChallengeGenerated for wrong client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintGenerateChallengeClient generateChallengeClient =
+ (FingerprintGenerateChallengeClient) client;
+ generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
+ });
+ }
+
+ @Override
+ public void onChallengeRevoked(long challenge) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintRevokeChallengeClient)) {
+ Slog.e(mTag, "onChallengeRevoked for wrong client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintRevokeChallengeClient revokeChallengeClient =
+ (FingerprintRevokeChallengeClient) client;
+ revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
+ });
+ }
+
+ @Override
+ public void onAcquired(byte info, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(mTag, "onAcquired for non-acquisition client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(info, vendorCode);
+ });
+ }
+
+ @Override
+ public void onError(byte error, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ Slog.d(mTag, "onError"
+ + ", client: " + Utils.getClientName(client)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof Interruptable)) {
+ Slog.e(mTag, "onError for non-error consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(error, vendorCode);
+
+ if (error == Error.HW_UNAVAILABLE) {
+ mCallback.onHardwareUnavailable();
+ }
+ });
+ }
+
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintEnrollClient)) {
+ Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final int currentUserId = client.getTargetUserId();
+ final CharSequence name = FingerprintUtils.getInstance(mSensorId)
+ .getUniqueName(mContext, currentUserId);
+ final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId);
+
+ final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
+ enrollClient.onEnrollResult(fingerprint, remaining);
+ });
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+
+ authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
+ });
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
+ authenticationConsumer
+ .onAuthenticated(fp, false /* authenticated */, null /* hat */);
+ });
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof LockoutConsumer)) {
+ Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+ lockoutConsumer.onLockoutTimed(durationMillis);
+ });
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof LockoutConsumer)) {
+ Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+ lockoutConsumer.onLockoutPermanent();
+ });
+ }
+
+ @Override
+ public void onLockoutCleared() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintResetLockoutClient)) {
+ Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintResetLockoutClient resetLockoutClient =
+ (FingerprintResetLockoutClient) client;
+ resetLockoutClient.onLockoutCleared();
+ });
+ }
+
+ @Override
+ public void onInteractionDetected() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintDetectClient)) {
+ Slog.e(mTag, "onInteractionDetected for non-detect client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintDetectClient fingerprintDetectClient =
+ (FingerprintDetectClient) client;
+ fingerprintDetectClient.onInteractionDetected();
+ });
+ }
+
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof EnumerateConsumer)) {
+ Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final EnumerateConsumer enumerateConsumer =
+ (EnumerateConsumer) client;
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
+ }
+ } else {
+ enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
+ }
+ });
+ }
+
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof RemovalConsumer)) {
+ Slog.e(mTag, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
+ }
+ } else {
+ removalConsumer.onRemoved(null, 0);
+ }
+ });
+ }
+
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
+ Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
+ (FingerprintGetAuthenticatorIdClient) client;
+ getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
+ });
+ }
+
+ @Override
+ public void onAuthenticatorIdInvalidated() {
+ // TODO(159667191)
+ }
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mTag = tag;
+ mProvider = provider;
mContext = context;
mHandler = handler;
mSensorProperties = sensorProperties;
mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
+ mLazySession = () -> {
+ if (mTestHalEnabled) {
+ return new TestSession(mCurrentSession.mHalSessionCallback);
+ } else {
+ return mCurrentSession != null ? mCurrentSession.mSession : null;
+ }
+ };
}
@NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -133,278 +446,31 @@
return mCurrentSession != null && mCurrentSession.mUserId == userId;
}
+ @Nullable Session getSessionForUser(int userId) {
+ if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
+ return mCurrentSession;
+ } else {
+ return null;
+ }
+ }
+
+ @NonNull ITestSession createTestSession() {
+ return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
+ }
+
void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
throws RemoteException {
- final ISessionCallback callback = new ISessionCallback.Stub() {
- @Override
- public void onStateChanged(int cookie, byte state) {
- // TODO(b/162973174)
- }
- @Override
- public void onChallengeGenerated(long challenge) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGenerateChallengeClient)) {
- Slog.e(mTag, "onChallengeGenerated for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGenerateChallengeClient generateChallengeClient =
- (FingerprintGenerateChallengeClient) client;
- generateChallengeClient.onChallengeGenerated(sensorId, userId, challenge);
- });
- }
-
- @Override
- public void onChallengeRevoked(long challenge) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintRevokeChallengeClient)) {
- Slog.e(mTag, "onChallengeRevoked for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintRevokeChallengeClient revokeChallengeClient =
- (FingerprintRevokeChallengeClient) client;
- revokeChallengeClient.onChallengeRevoked(sensorId, userId, challenge);
- });
- }
-
- @Override
- public void onAcquired(byte info, int vendorCode) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(mTag, "onAcquired for non-acquisition client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(info, vendorCode);
- });
- }
-
- @Override
- public void onError(byte error, int vendorCode) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- Slog.d(mTag, "onError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof Interruptable)) {
- Slog.e(mTag, "onError for non-error consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final Interruptable interruptable = (Interruptable) client;
- interruptable.onError(error, vendorCode);
-
- if (error == Error.HW_UNAVAILABLE) {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- }
- });
- }
-
- @Override
- public void onEnrollmentProgress(int enrollmentId, int remaining) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintEnrollClient)) {
- Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FingerprintUtils.getInstance(sensorId)
- .getUniqueName(mContext, currentUserId);
- final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, sensorId);
-
- final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
- enrollClient.onEnrollResult(fingerprint, remaining);
- });
- }
-
- @Override
- public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", enrollmentId, sensorId);
- final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
- final ArrayList<Byte> byteList = new ArrayList<>();
- for (byte b : byteArray) {
- byteList.add(b);
- }
-
- authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
- });
- }
-
- @Override
- public void onAuthenticationFailed() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, sensorId);
- authenticationConsumer
- .onAuthenticated(fp, false /* authenticated */, null /* hat */);
- });
- }
-
- @Override
- public void onLockoutTimed(long durationMillis) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutTimed(durationMillis);
- });
- }
-
- @Override
- public void onLockoutPermanent() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutPermanent();
- });
- }
-
- @Override
- public void onLockoutCleared() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintResetLockoutClient)) {
- Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintResetLockoutClient resetLockoutClient =
- (FingerprintResetLockoutClient) client;
- resetLockoutClient.onLockoutCleared();
- });
- }
-
- @Override
- public void onInteractionDetected() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintDetectClient)) {
- Slog.e(mTag, "onInteractionDetected for non-detect client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintDetectClient fingerprintDetectClient =
- (FingerprintDetectClient) client;
- fingerprintDetectClient.onInteractionDetected();
- });
- }
-
- @Override
- public void onEnrollmentsEnumerated(int[] enrollmentIds) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer =
- (EnumerateConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], sensorId);
- enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
- }
- } else {
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onEnrollmentsRemoved(int[] enrollmentIds) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(mTag, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], sensorId);
- removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
- }
- } else {
- removalConsumer.onRemoved(null, 0);
- }
- });
- }
-
- @Override
- public void onAuthenticatorIdRetrieved(long authenticatorId) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
- Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
- (FingerprintGetAuthenticatorIdClient) client;
- getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
- });
- }
-
- @Override
- public void onAuthenticatorIdInvalidated() {
- // TODO(159667191)
- }
+ final HalSessionCallback.Callback callback = () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
};
+ final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
+ mTag, mScheduler, sensorId, userId, callback);
- final ISession newSession = daemon.createSession(sensorId, userId, callback);
+ final ISession newSession = daemon.createSession(sensorId, userId, resultController);
newSession.asBinder().linkToDeath(this, 0 /* flags */);
- mCurrentSession = new Session(mTag, newSession, userId, callback);
+ mCurrentSession = new Session(mTag, newSession, userId, resultController);
}
@NonNull BiometricScheduler getScheduler() {
@@ -418,4 +484,27 @@
@NonNull Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
+
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+ final long sensorToken = proto.start(FingerprintServiceStateProto.SENSOR_STATES);
+
+ proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
+ proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(SensorStateProto.USER_STATES);
+ proto.write(UserStateProto.USER_ID, userId);
+ proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId).size());
+ proto.end(userToken);
+ }
+
+ proto.end(sensorToken);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
new file mode 100644
index 0000000..8c9a269
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Test HAL that provides only provides no-ops.
+ */
+public class TestHal extends IFingerprint.Stub {
+ @Override
+ public SensorProps[] getSensorProps() {
+ return new SensorProps[0];
+ }
+
+ @Override
+ public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
+ return new ISession() {
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) {
+
+ }
+
+ @Override
+ public void revokeChallenge(int cookie, long challenge) {
+
+ }
+
+ @Override
+ public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal authenticate(int cookie, long operationId) {
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction(int cookie) {
+ return null;
+ }
+
+ @Override
+ public void enumerateEnrollments(int cookie) {
+
+ }
+
+ @Override
+ public void removeEnrollments(int cookie, int[] enrollmentIds) {
+
+ }
+
+ @Override
+ public void getAuthenticatorId(int cookie) {
+
+ }
+
+ @Override
+ public void invalidateAuthenticatorId(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void resetLockout(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
+
+ }
+
+ @Override
+ public void onPointerUp(int pointerId) {
+
+ }
+
+ @Override
+ public void onUiReady() {
+
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return new Binder();
+ }
+ };
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
new file mode 100644
index 0000000..d5afd0c
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+/**
+ * Test HAL that provides only provides mostly no-ops.
+ */
+class TestSession extends ISession.Stub {
+
+ private static final String TAG = "TestSession";
+
+ @NonNull private final Sensor.HalSessionCallback mHalSessionCallback;
+
+ TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
+ mHalSessionCallback = halSessionCallback;
+ }
+
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) {
+
+ }
+
+ @Override
+ public void revokeChallenge(int cookie, long challenge) {
+
+ }
+
+ @Override
+ public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+ Slog.d(TAG, "enroll");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal authenticate(int cookie, long operationId) {
+ Slog.d(TAG, "authenticate");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction(int cookie) {
+ return null;
+ }
+
+ @Override
+ public void enumerateEnrollments(int cookie) {
+ Slog.d(TAG, "enumerate");
+ }
+
+ @Override
+ public void removeEnrollments(int cookie, int[] enrollmentIds) {
+ Slog.d(TAG, "remove");
+ }
+
+ @Override
+ public void getAuthenticatorId(int cookie) {
+ Slog.d(TAG, "getAuthenticatorId");
+ // Immediately return a value so the framework can continue with subsequent requests.
+ mHalSessionCallback.onAuthenticatorIdRetrieved(0);
+ }
+
+ @Override
+ public void invalidateAuthenticatorId(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void resetLockout(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
+
+ }
+
+ @Override
+ public void onPointerUp(int pointerId) {
+
+ }
+
+ @Override
+ public void onUiReady() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
new file mode 100644
index 0000000..e0ea990
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import static android.Manifest.permission.TEST_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * A test session implementation for the {@link Fingerprint21} provider. See
+ * {@link android.hardware.biometrics.BiometricTestSession}.
+ */
+public class BiometricTestSessionImpl extends ITestSession.Stub {
+
+ private static final String TAG = "BiometricTestSessionImpl";
+
+ @NonNull private final Context mContext;
+ private final int mSensorId;
+ @NonNull private final Fingerprint21 mFingerprint21;
+ @NonNull private final Fingerprint21.HalResultController mHalResultController;
+ @NonNull private final Set<Integer> mEnrollmentIds;
+ @NonNull private final Random mRandom;
+
+ /**
+ * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
+ * test, since enrollment is a platform-only API. The authentication path is tested through
+ * the public FingerprintManager APIs and does not use this receiver.
+ */
+ private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
+ @Override
+ public void onEnrollResult(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(Fingerprint fp, int userId,
+ boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+
+ }
+
+ @Override
+ public void onError(int error, int vendorCode) {
+
+ }
+
+ @Override
+ public void onRemoved(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onChallengeGenerated(int sensorId, long challenge) {
+
+ }
+ };
+
+ BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+ @NonNull Fingerprint21 fingerprint21,
+ @NonNull Fingerprint21.HalResultController halResultController) {
+ mContext = context;
+ mSensorId = sensorId;
+ mFingerprint21 = fingerprint21;
+ mHalResultController = halResultController;
+ mEnrollmentIds = new HashSet<>();
+ mRandom = new Random();
+ }
+
+ @Override
+ public void setTestHalEnabled(boolean enabled) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mFingerprint21.setTestHalEnabled(enabled);
+ }
+
+ @Override
+ public void startEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
+ mContext.getOpPackageName(), null /* surface */);
+ }
+
+ @Override
+ public void finishEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ int nextRandomId = mRandom.nextInt();
+ while (mEnrollmentIds.contains(nextRandomId)) {
+ nextRandomId = mRandom.nextInt();
+ }
+
+ mEnrollmentIds.add(nextRandomId);
+ mHalResultController.onEnrollResult(0 /* deviceId */,
+ nextRandomId /* fingerId */, userId, 0);
+ }
+
+ @Override
+ public void acceptAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ // Fake authentication with any of the existing fingers
+ List<Fingerprint> fingerprints = FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId);
+ if (fingerprints.isEmpty()) {
+ Slog.w(TAG, "No fingerprints, returning");
+ return;
+ }
+ final int fid = fingerprints.get(0).getBiometricId();
+ final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
+ mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
+ }
+
+ @Override
+ public void rejectAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
+ }
+
+ @Override
+ public void notifyAcquired(int userId, int acquireInfo) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
+ }
+
+ @Override
+ public void notifyError(int userId, int errorCode) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
+ }
+
+ @Override
+ public void cleanupInternalState(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mFingerprint21.scheduleInternalCleanup(mSensorId, userId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index ab4427c..241c911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -51,8 +52,11 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
+import com.android.server.biometrics.fingerprint.FingerprintServiceStateProto;
import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.fingerprint.SensorStateProto;
+import com.android.server.biometrics.fingerprint.UserStateProto;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
@@ -91,6 +95,8 @@
private static final String TAG = "Fingerprint21";
private static final int ENROLL_TIMEOUT_SEC = 60;
+ private boolean mTestHalEnabled;
+
final Context mContext;
private final IActivityTaskManager mActivityTaskManager;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
@@ -391,6 +397,10 @@
}
private synchronized IBiometricsFingerprint getDaemon() {
+ if (mTestHalEnabled) {
+ return new TestHal();
+ }
+
if (mDaemon != null) {
return mDaemon;
}
@@ -693,7 +703,27 @@
}
@Override
- public void dumpProto(int sensorId, FileDescriptor fd) {
+ public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+ final long sensorToken = proto.start(FingerprintServiceStateProto.SENSOR_STATES);
+
+ proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
+ proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(SensorStateProto.USER_STATES);
+ proto.write(UserStateProto.USER_ID, userId);
+ proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId).size());
+ proto.end(userToken);
+ }
+
+ proto.end(sensorToken);
+ }
+
+ @Override
+ public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
PerformanceTracker tracker =
PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
@@ -771,4 +801,15 @@
pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
mScheduler.dump(pw);
}
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
+
+ @NonNull
+ @Override
+ public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+ return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, this,
+ mHalResultController);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
new file mode 100644
index 0000000..86c0875
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
+import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
+import android.os.RemoteException;
+
+/**
+ * Test HAL that provides only provides no-ops.
+ */
+public class TestHal extends IBiometricsFingerprint.Stub {
+ @Override
+ public boolean isUdfps(int sensorId) {
+ return false;
+ }
+
+ @Override
+ public void onFingerDown(int x, int y, float minor, float major) {
+
+ }
+
+ @Override
+ public void onFingerUp() {
+
+ }
+
+ @Override
+ public long setNotify(IBiometricsFingerprintClientCallback clientCallback) {
+ return 0;
+ }
+
+ @Override
+ public long preEnroll() {
+ return 0;
+ }
+
+ @Override
+ public int enroll(byte[] hat, int gid, int timeoutSec) {
+ return 0;
+ }
+
+ @Override
+ public int postEnroll() {
+ return 0;
+ }
+
+ @Override
+ public long getAuthenticatorId() {
+ return 0;
+ }
+
+ @Override
+ public int cancel() {
+ return 0;
+ }
+
+ @Override
+ public int enumerate() {
+ return 0;
+ }
+
+ @Override
+ public int remove(int gid, int fid) {
+ return 0;
+ }
+
+ @Override
+ public int setActiveGroup(int gid, String storePath) {
+ return 0;
+ }
+
+ @Override
+ public int authenticate(long operationId, int gid) {
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index a0eafb4..b7e188c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -392,6 +392,7 @@
} catch (RemoteException ex) {
Slog.e(TAG, "Failed closing announcement listener", ex);
}
+ hwCloseHandle.value = null;
}
};
}
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 15f43a0..fbd089c 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -52,7 +52,6 @@
private SignalStrength mSignalStrength;
private ServiceState mServiceState;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
- private int mNrState = NetworkRegistrationInfo.NR_STATE_NONE;
public DataConnectionStats(Context context, Handler listenerHandler) {
mContext = context;
@@ -100,7 +99,7 @@
: regInfo.getAccessNetworkTechnology();
// If the device is in NSA NR connection the networkType will report as LTE.
// For cell dwell rate metrics, this should report NR instead.
- if (mNrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
+ if (regInfo != null && regInfo.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
networkType = TelephonyManager.NETWORK_TYPE_NR;
}
if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
@@ -172,7 +171,6 @@
@Override
public void onServiceStateChanged(ServiceState state) {
mServiceState = state;
- mNrState = state.getNrState();
notePhoneDataConnectionState();
}
diff --git a/services/core/java/com/android/server/connectivity/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java
index 7fdc7a0..f99f4c6 100644
--- a/services/core/java/com/android/server/connectivity/LingerMonitor.java
+++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java
@@ -159,9 +159,11 @@
@VisibleForTesting
protected PendingIntent createNotificationIntent() {
- return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, CELLULAR_SETTINGS,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- null /* options */, UserHandle.CURRENT);
+ return PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
+ 0 /* requestCode */,
+ CELLULAR_SETTINGS,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
// Removes any notification that was put up as a result of switching to nai.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e218793..4c2b1e3 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1969,8 +1969,9 @@
*/
public PendingIntent pendingIntentGetActivityAsUser(
Intent intent, int flags, UserHandle user) {
- return PendingIntent.getActivityAsUser(mContext, 0 /*request*/, intent, flags,
- null /*options*/, user);
+ return PendingIntent.getActivity(
+ mContext.createContextAsUser(user, 0 /* flags */), 0 /* requestCode */,
+ intent, flags);
}
/**
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index c686ba4..6a4ca8d 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -210,7 +210,7 @@
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
- private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = false;
+ private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = true;
private static final int SYNC_OP_STATE_VALID = 0;
// "1" used to include errors 3, 4 and 5 but now it's split up.
@@ -231,7 +231,7 @@
private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
- // TODO: add better locking around mRunningAccounts
+ private final Object mAccountsLock = new Object();
private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
@@ -933,19 +933,21 @@
}
AccountAndUser[] accounts = null;
- if (requestedAccount != null) {
- if (userId != UserHandle.USER_ALL) {
- accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)};
- } else {
- for (AccountAndUser runningAccount : mRunningAccounts) {
- if (requestedAccount.equals(runningAccount.account)) {
- accounts = ArrayUtils.appendElement(AccountAndUser.class,
- accounts, runningAccount);
+ synchronized (mAccountsLock) {
+ if (requestedAccount != null) {
+ if (userId != UserHandle.USER_ALL) {
+ accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)};
+ } else {
+ for (AccountAndUser runningAccount : mRunningAccounts) {
+ if (requestedAccount.equals(runningAccount.account)) {
+ accounts = ArrayUtils.appendElement(AccountAndUser.class,
+ accounts, runningAccount);
+ }
}
}
+ } else {
+ accounts = mRunningAccounts;
}
- } else {
- accounts = mRunningAccounts;
}
if (ArrayUtils.isEmpty(accounts)) {
@@ -3228,40 +3230,43 @@
}
private void updateRunningAccountsH(EndPoint syncTargets) {
- AccountAndUser[] oldAccounts = mRunningAccounts;
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "Accounts list: ");
- for (AccountAndUser acc : mRunningAccounts) {
- Slog.v(TAG, acc.toString());
+ synchronized (mAccountsLock) {
+ AccountAndUser[] oldAccounts = mRunningAccounts;
+ mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, "Accounts list: ");
+ for (AccountAndUser acc : mRunningAccounts) {
+ Slog.v(TAG, acc.toString());
+ }
}
- }
- if (mLogger.enabled()) {
- mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
- }
- removeStaleAccounts();
-
- AccountAndUser[] accounts = mRunningAccounts;
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- if (!containsAccountAndUser(accounts,
- currentSyncContext.mSyncOperation.target.account,
- currentSyncContext.mSyncOperation.target.userId)) {
- Log.d(TAG, "canceling sync since the account is no longer running");
- sendSyncFinishedOrCanceledMessage(currentSyncContext,
- null /* no result since this is a cancel */);
+ if (mLogger.enabled()) {
+ mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
}
- }
+ removeStaleAccounts();
- if (syncTargets != null) {
- // On account add, check if there are any settings to be restored.
- for (AccountAndUser aau : mRunningAccounts) {
- if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Account " + aau.account
- + " added, checking sync restore data");
+ AccountAndUser[] accounts = mRunningAccounts;
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ if (!containsAccountAndUser(accounts,
+ currentSyncContext.mSyncOperation.target.account,
+ currentSyncContext.mSyncOperation.target.userId)) {
+ Log.d(TAG, "canceling sync since the account is no longer running");
+ sendSyncFinishedOrCanceledMessage(currentSyncContext,
+ null /* no result since this is a cancel */);
+ }
+ }
+
+ if (syncTargets != null) {
+ // On account add, check if there are any settings to be restored.
+ for (AccountAndUser aau : mRunningAccounts) {
+ if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Account " + aau.account
+ + " added, checking sync restore data");
+ }
+ AccountSyncSettingsBackupHelper.accountAdded(mContext,
+ syncTargets.userId);
+ break;
}
- AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId);
- break;
}
}
}
@@ -3442,13 +3447,15 @@
final EndPoint target = op.target;
// Drop the sync if the account of this operation no longer exists.
- AccountAndUser[] accounts = mRunningAccounts;
- if (!containsAccountAndUser(accounts, target.account, target.userId)) {
- if (isLoggable) {
- Slog.v(TAG, " Dropping sync operation: account doesn't exist.");
+ synchronized (mAccountsLock) {
+ AccountAndUser[] accounts = mRunningAccounts;
+ if (!containsAccountAndUser(accounts, target.account, target.userId)) {
+ if (isLoggable) {
+ Slog.v(TAG, " Dropping sync operation: account doesn't exist.");
+ }
+ logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist.");
+ return SYNC_OP_STATE_INVALID_NO_ACCOUNT;
}
- logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist.");
- return SYNC_OP_STATE_INVALID_NO_ACCOUNT;
}
// Drop this sync request if it isn't syncable.
state = computeSyncable(target.account, target.userId, target.provider, true);
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index a9e8719..8980de1 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -339,7 +339,8 @@
// This is to manager CEC device separately in case they don't have address.
if (mIsTvDevice) {
- tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
+ localDevice().mService.getHdmiCecNetwork().updateCecSwitchInfo(current.mLogicalAddress,
+ current.mDeviceType,
current.mPhysicalAddress);
}
increaseProcessedDeviceCount();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 96679c3..efe7302 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -32,7 +32,6 @@
import android.os.RemoteException;
import android.stats.hdmi.HdmiStatsEnums;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
@@ -97,7 +96,7 @@
private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
@Override
public boolean test(Integer address) {
- return !isAllocatedLocalDeviceAddress(address);
+ return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address);
}
};
@@ -118,9 +117,6 @@
private final HdmiControlService mService;
- // Stores the local CEC devices in the system. Device type is used for key.
- private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
-
// Stores recent CEC messages and HDMI Hotplug event history for debugging purpose.
private final ArrayBlockingQueue<Dumpable> mMessageHistory =
new ArrayBlockingQueue<>(MAX_HDMI_MESSAGE_HISTORY);
@@ -173,12 +169,6 @@
nativeWrapper.setCallback(new HdmiCecCallback());
}
- @ServiceThreadOnly
- void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
- assertRunOnServiceThread();
- mLocalDevices.put(deviceType, device);
- }
-
/**
* Allocate a new logical address of the given device type. Allocated
* address will be reported through {@link AllocateAddressCallback}.
@@ -269,17 +259,6 @@
}
/**
- * Return the locally hosted logical device of a given type.
- *
- * @param deviceType logical device type
- * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
- * otherwise null.
- */
- HdmiCecLocalDevice getLocalDevice(int deviceType) {
- return mLocalDevices.get(deviceType);
- }
-
- /**
* Add a new logical address to the device. Device's HW should be notified
* when a new logical address is assigned to a device, so that it can accept
* a command having available destinations.
@@ -307,18 +286,9 @@
@ServiceThreadOnly
void clearLogicalAddress() {
assertRunOnServiceThread();
- for (int i = 0; i < mLocalDevices.size(); ++i) {
- mLocalDevices.valueAt(i).clearAddress();
- }
mNativeWrapperImpl.nativeClearLogicalAddress();
}
- @ServiceThreadOnly
- void clearLocalDevices() {
- assertRunOnServiceThread();
- mLocalDevices.clear();
- }
-
/**
* Return the physical address of the device.
*
@@ -428,17 +398,6 @@
runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
}
- /**
- * Return a list of all {@link HdmiCecLocalDevice}s.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- */
- @ServiceThreadOnly
- List<HdmiCecLocalDevice> getLocalDeviceList() {
- assertRunOnServiceThread();
- return HdmiUtils.sparseArrayToList(mLocalDevices);
- }
-
private List<Integer> pickPollCandidates(int pickStrategy) {
int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Predicate<Integer> pickPredicate = null;
@@ -475,17 +434,6 @@
}
@ServiceThreadOnly
- private boolean isAllocatedLocalDeviceAddress(int address) {
- assertRunOnServiceThread();
- for (int i = 0; i < mLocalDevices.size(); ++i) {
- if (mLocalDevices.valueAt(i).isAddressOf(address)) {
- return true;
- }
- }
- return false;
- }
-
- @ServiceThreadOnly
private void runDevicePolling(final int sourceAddress,
final List<Integer> candidates, final int retryCount,
final DevicePollingCallback callback, final List<Integer> allocated) {
@@ -578,7 +526,7 @@
if (address == Constants.ADDR_BROADCAST) {
return true;
}
- return isAllocatedLocalDeviceAddress(address);
+ return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address);
}
@ServiceThreadOnly
@@ -682,7 +630,7 @@
private int incomingMessageDirection(int srcAddress, int dstAddress) {
boolean sourceIsLocal = false;
boolean destinationIsLocal = false;
- for (HdmiCecLocalDevice localDevice : getLocalDeviceList()) {
+ for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) {
int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress();
if (logicalAddress == srcAddress) {
sourceIsLocal = true;
@@ -731,24 +679,9 @@
}
void dump(final IndentingPrintWriter pw) {
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
- for (int i = 0; i < mLocalDevices.size(); ++i) {
- pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
- pw.increaseIndent();
- mLocalDevices.valueAt(i).dump(pw);
-
- pw.println("Active Source history:");
- pw.increaseIndent();
- for (Dumpable activeSourceEvent : mLocalDevices.valueAt(i).getActiveSourceHistory()) {
- activeSourceEvent.dump(pw, sdf);
- }
- pw.decreaseIndent();
- pw.decreaseIndent();
- }
-
pw.println("CEC message history:");
pw.increaseIndent();
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Dumpable record : mMessageHistory) {
record.dump(pw, sdf);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 946fb0d..62a67b6 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -471,8 +472,27 @@
return false;
}
+ @CallSuper
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
- return false;
+ // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network
+ // state
+
+ int address = message.getSource();
+
+ // Ignore if [Device Discovery Action] is going on.
+ if (hasAction(DeviceDiscoveryAction.class)) {
+ Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
+ return true;
+ }
+
+ HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address);
+ // If no non-default display name is available for the device, request the devices OSD name.
+ if (cecDeviceInfo.getDisplayName().equals(HdmiUtils.getDefaultDeviceName(address))) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
+ }
+
+ return true;
}
protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
@@ -708,7 +728,7 @@
}
protected boolean handleSetOsdName(HdmiCecMessage message) {
- // The default behavior of <Set Osd Name> is doing nothing.
+ // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state
return true;
}
@@ -724,7 +744,8 @@
}
protected boolean handleReportPowerStatus(HdmiCecMessage message) {
- return false;
+ // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state
+ return true;
}
protected boolean handleTimerStatus(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 29bdd6c..fe4fd38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -37,7 +37,6 @@
import android.provider.Settings.Global;
import android.sysprop.HdmiProperties;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -54,15 +53,12 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
-
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
* system.
@@ -104,14 +100,6 @@
@GuardedBy("mLock")
private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>();
- // Copy of mDeviceInfos to guarantee thread-safety.
- @GuardedBy("mLock")
- private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
-
- // Map-like container of all cec devices.
- // device id is used as key of container.
- private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
-
// Message buffer used to buffer selected messages to process later. <Active Source>
// from a source device, for instance, needs to be buffered if the device is not
// discovered yet. The buffered commands are taken out and when they are ready to
@@ -187,135 +175,6 @@
return info != null;
}
- /**
- * Called when a device is newly added or a new device is detected or
- * an existing device is updated.
- *
- * @param info device info of a new device.
- */
- @ServiceThreadOnly
- final void addCecDevice(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- HdmiDeviceInfo old = addDeviceInfo(info);
- if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
- // The addition of the device itself should not be notified.
- // Note that different logical address could still be the same local device.
- return;
- }
- if (old == null) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- } else if (!old.equals(info)) {
- invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- }
- }
-
- /**
- * Called when a device is removed or removal of device is detected.
- *
- * @param address a logical address of a device to be removed
- */
- @ServiceThreadOnly
- final void removeCecDevice(int address) {
- assertRunOnServiceThread();
- HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
- mCecMessageCache.flushMessagesFrom(address);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
-
- /**
- * Called when a device is updated.
- *
- * @param info device info of the updating device.
- */
- @ServiceThreadOnly
- final void updateCecDevice(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- HdmiDeviceInfo old = addDeviceInfo(info);
-
- if (old == null) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- } else if (!old.equals(info)) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
- }
- }
-
- /**
- * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
- * logical address as new device info's.
- *
- * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
- * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
- * that has the same logical address as new one has.
- */
- @ServiceThreadOnly
- @VisibleForTesting
- protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
- assertRunOnServiceThread();
- mService.checkLogicalAddressConflictAndReallocate(deviceInfo.getLogicalAddress());
- HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
- if (oldDeviceInfo != null) {
- removeDeviceInfo(deviceInfo.getId());
- }
- mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
- updateSafeDeviceInfoList();
- return oldDeviceInfo;
- }
-
- /**
- * Remove a device info corresponding to the given {@code logicalAddress}.
- * It returns removed {@link HdmiDeviceInfo} if exists.
- *
- * @param id id of device to be removed
- * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
- */
- @ServiceThreadOnly
- private HdmiDeviceInfo removeDeviceInfo(int id) {
- assertRunOnServiceThread();
- HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
- if (deviceInfo != null) {
- mDeviceInfos.remove(id);
- }
- updateSafeDeviceInfoList();
- return deviceInfo;
- }
-
- /**
- * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
- *
- * @param logicalAddress logical address of the device to be retrieved
- * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
- * Returns null if no logical address matched
- */
- @ServiceThreadOnly
- HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
- assertRunOnServiceThread();
- return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
- }
-
- @ServiceThreadOnly
- private void updateSafeDeviceInfoList() {
- assertRunOnServiceThread();
- List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
- synchronized (mLock) {
- mSafeAllDeviceInfos = copiedDevices;
- }
- }
-
- @GuardedBy("mLock")
- List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- infoList.add(info);
- }
- return infoList;
- }
-
- private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
- mService.invokeDeviceEventListeners(info, status);
- }
-
@Override
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
@@ -342,7 +201,7 @@
}
// Update with TIF on the device removal. TIF callback will update
// mPortIdToTvInputs and mPortIdToTvInputs.
- removeCecDevice(info.getLogicalAddress());
+ mService.getHdmiCecNetwork().removeCecDevice(this, info.getLogicalAddress());
}
}
@@ -399,7 +258,7 @@
boolean lastSystemAudioControlStatus =
SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
- clearDeviceInfoList();
+ mService.getHdmiCecNetwork().clearDeviceList();
launchDeviceDiscovery();
startQueuedActions();
}
@@ -458,7 +317,7 @@
// If the new Active Source is under the current device, check if the device info and the TV
// input is ready to switch to the new Active Source. If not ready, buffer the cec command
// to handle later when the device is ready.
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
if (info == null) {
HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
mDelayedMessageBuffer.add(message);
@@ -474,79 +333,6 @@
@Override
@ServiceThreadOnly
- protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int path = HdmiUtils.twoBytesToInt(message.getParams());
- int address = message.getSource();
- int type = message.getParams()[2];
-
- // Ignore if [Device Discovery Action] is going on.
- if (hasAction(DeviceDiscoveryAction.class)) {
- Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
- return true;
- }
-
- // Update the device info with TIF, note that the same device info could have added in
- // device discovery and we do not want to override it with default OSD name. Therefore we
- // need the following check to skip redundant device info updating.
- HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
- if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
- addCecDevice(new HdmiDeviceInfo(
- address, path, mService.pathToPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, ""));
- // if we are adding a new device info, send out a give osd name command
- // to update the name of the device in TIF
- mService.sendCecCommand(
- HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
- return true;
- }
-
- Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
- return true;
- }
-
- @Override
- protected boolean handleReportPowerStatus(HdmiCecMessage command) {
- int newStatus = command.getParams()[0] & 0xFF;
- updateDevicePowerStatus(command.getSource(), newStatus);
- return true;
- }
-
- @Override
- @ServiceThreadOnly
- protected boolean handleSetOsdName(HdmiCecMessage message) {
- int source = message.getSource();
- String osdName;
- HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
- // If the device is not in device list, ignore it.
- if (deviceInfo == null) {
- Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
- return true;
- }
- try {
- osdName = new String(message.getParams(), "US-ASCII");
- } catch (UnsupportedEncodingException e) {
- Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
- return true;
- }
-
- if (deviceInfo.getDisplayName() != null
- && deviceInfo.getDisplayName().equals(osdName)) {
- Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
- return true;
- }
-
- Slog.d(TAG, "Updating device OSD name from "
- + deviceInfo.getDisplayName()
- + " to " + osdName);
- updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
- deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
- return true;
- }
-
- @Override
- @ServiceThreadOnly
protected boolean handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement initiate arc handler
@@ -864,14 +650,9 @@
!= HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
return true;
}
- boolean isDeviceInCecDeviceList = false;
- for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
- if (info.getPhysicalAddress() == sourcePhysicalAddress) {
- isDeviceInCecDeviceList = true;
- break;
- }
- }
- if (!isDeviceInCecDeviceList) {
+ HdmiDeviceInfo safeDeviceInfoByPath =
+ mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress);
+ if (safeDeviceInfoByPath == null) {
switchInputOnReceivingNewActivePath(sourcePhysicalAddress);
}
}
@@ -1345,24 +1126,6 @@
routeToInputFromPortId(getRoutingPort());
}
- protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
- if (info == null) {
- Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
- return;
- }
-
- if (info.getDevicePowerStatus() == newPowerStatus) {
- return;
- }
-
- HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
- // addDeviceInfo replaces old device info with new one if exists.
- addDeviceInfo(newInfo);
-
- invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
- }
-
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
@@ -1375,27 +1138,13 @@
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
- addCecDevice(info);
+ mService.getHdmiCecNetwork().addCecDevice(info);
}
}
});
addAndStartAction(action);
}
- // Clear all device info.
- @ServiceThreadOnly
- private void clearDeviceInfoList() {
- assertRunOnServiceThread();
- for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
- if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
- continue;
- }
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
- mDeviceInfos.clear();
- updateSafeDeviceInfoList();
- }
-
@Override
protected void dump(IndentingPrintWriter pw) {
pw.println("HdmiCecLocalDeviceAudioSystem:");
@@ -1409,7 +1158,6 @@
pw.println("mLocalActivePort: " + getLocalActivePort());
HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs);
HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo);
- HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
pw.decreaseIndent();
super.dump(pw);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 0325ad9..93cdca2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -42,9 +42,7 @@
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager.TvInputCallback;
import android.provider.Settings.Global;
-import android.util.ArraySet;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
@@ -53,13 +51,8 @@
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
/**
@@ -95,37 +88,18 @@
@GuardedBy("mLock")
private boolean mSystemAudioMute = false;
- // Copy of mDeviceInfos to guarantee thread-safety.
- @GuardedBy("mLock")
- private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
- // All external cec input(source) devices. Does not include system audio device.
- @GuardedBy("mLock")
- private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
-
- // Map-like container of all cec devices including local ones.
- // device id is used as key of container.
- // This is not thread-safe. For external purpose use mSafeDeviceInfos.
- private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
-
// If true, TV going to standby mode puts other devices also to standby.
private boolean mAutoDeviceOff;
// If true, TV wakes itself up when receiving <Text/Image View On>.
private boolean mAutoWakeup;
- // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
- private List<Integer> mLocalDeviceAddresses;
-
private final HdmiCecStandbyModeHandler mStandbyHandler;
// If true, do not do routing control/send active source for internal source.
// Set to true when the device was woken up by <Text/Image View On>.
private boolean mSkipRoutingControl;
- // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
- // other CEC devices since they might not have logical address.
- private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
-
// Message buffer used to buffer selected messages to process later. <Active Source>
// from a source device, for instance, needs to be buffered if the device is not
// discovered yet. The buffered commands are taken out and when they are ready to
@@ -205,12 +179,12 @@
mAddress, mService.getPhysicalAddress(), mDeviceType));
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
mAddress, mService.getVendorId()));
- mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too.
+ mService.getHdmiCecNetwork().addCecSwitch(
+ mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too.
mTvInputs.clear();
mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
reason != HdmiControlService.INITIATED_BY_BOOT_UP);
- mLocalDeviceAddresses = initLocalDeviceAddresses();
resetSelectRequestBuffer();
launchDeviceDiscovery();
startQueuedActions();
@@ -220,17 +194,6 @@
}
@ServiceThreadOnly
- private List<Integer> initLocalDeviceAddresses() {
- assertRunOnServiceThread();
- List<Integer> addresses = new ArrayList<>();
- for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
- addresses.add(device.getDeviceInfo().getLogicalAddress());
- }
- return Collections.unmodifiableList(addresses);
- }
-
-
- @ServiceThreadOnly
public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
assertRunOnServiceThread();
mSelectRequestBuffer = requestBuffer;
@@ -272,7 +235,7 @@
@ServiceThreadOnly
void deviceSelect(int id, IHdmiControlCallback callback) {
assertRunOnServiceThread();
- HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
+ HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
if (targetDevice == null) {
invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
return;
@@ -335,7 +298,8 @@
}
setActiveSource(newActive, caller);
int logicalAddress = newActive.logicalAddress;
- if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
+ if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null
+ && logicalAddress != mAddress) {
if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
setPrevPortId(getActivePortId());
}
@@ -374,7 +338,8 @@
// Show OSD port change banner
if (notifyInputChange) {
ActiveSource activeSource = getActiveSource();
- HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(
+ activeSource.logicalAddress);
if (info == null) {
info = mService.getDeviceInfoByPort(getActivePortId());
if (info == null) {
@@ -442,7 +407,7 @@
if (getActiveSource().isValid()) {
return getActiveSource().logicalAddress;
}
- HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath());
if (info != null) {
return info.getLogicalAddress();
}
@@ -455,7 +420,7 @@
assertRunOnServiceThread();
int logicalAddress = message.getSource();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
if (info == null) {
if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
@@ -463,7 +428,8 @@
}
} else if (isInputReady(info.getId())
|| info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
- updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
+ mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
+ HdmiControlManager.POWER_STATUS_ON);
ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
} else {
@@ -490,7 +456,8 @@
if (portId != Constants.INVALID_PORT_ID) {
// TODO: Do this only if TV is not showing multiview like PIP/PAP.
- HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
+ HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo(
+ message.getSource());
if (inactiveSource == null) {
return true;
}
@@ -546,42 +513,20 @@
}
@Override
- @ServiceThreadOnly
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
- assertRunOnServiceThread();
+ super.handleReportPhysicalAddress(message);
int path = HdmiUtils.twoBytesToInt(message.getParams());
int address = message.getSource();
int type = message.getParams()[2];
- if (updateCecSwitchInfo(address, type, path)) return true;
-
- // Ignore if [Device Discovery Action] is going on.
- if (hasAction(DeviceDiscoveryAction.class)) {
- Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
- return true;
- }
-
- if (!isInDeviceList(address, path)) {
+ if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
handleNewDeviceAtTheTailOfActivePath(path);
}
-
- // Add the device ahead with default information to handle <Active Source>
- // promptly, rather than waiting till the new device action is finished.
- HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
- addCecDevice(deviceInfo);
startNewDeviceAction(ActiveSource.of(address, path), type);
return true;
}
@Override
- protected boolean handleReportPowerStatus(HdmiCecMessage command) {
- int newStatus = command.getParams()[0] & 0xFF;
- updateDevicePowerStatus(command.getSource(), newStatus);
- return true;
- }
-
- @Override
protected boolean handleTimerStatus(HdmiCecMessage message) {
// Do nothing.
return true;
@@ -593,19 +538,6 @@
return true;
}
- boolean updateCecSwitchInfo(int address, int type, int path) {
- if (address == Constants.ADDR_UNREGISTERED
- && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
- mCecSwitches.add(path);
- updateSafeDeviceInfoList();
- return true; // Pure switch does not need further processing. Return here.
- }
- if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
- mCecSwitches.add(path);
- }
- return false;
- }
-
void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
// If there is new device action which has the same logical address and path
@@ -719,35 +651,6 @@
return handleTextViewOn(message);
}
- @Override
- @ServiceThreadOnly
- protected boolean handleSetOsdName(HdmiCecMessage message) {
- int source = message.getSource();
- HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
- // If the device is not in device list, ignore it.
- if (deviceInfo == null) {
- Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
- return true;
- }
- String osdName = null;
- try {
- osdName = new String(message.getParams(), "US-ASCII");
- } catch (UnsupportedEncodingException e) {
- Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
- return true;
- }
-
- if (deviceInfo.getDisplayName().equals(osdName)) {
- Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
- return true;
- }
-
- addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
- deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
- return true;
- }
-
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
@@ -757,14 +660,14 @@
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
- addCecDevice(info);
+ mService.getHdmiCecNetwork().addCecDevice(info);
}
// Since we removed all devices when it's start and
// device discovery action does not poll local devices,
// we should put device info of local device manually here
for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
- addCecDevice(device.getDeviceInfo());
+ mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
}
mSelectRequestBuffer.process();
@@ -798,11 +701,7 @@
@ServiceThreadOnly
private void clearDeviceInfoList() {
assertRunOnServiceThread();
- for (HdmiDeviceInfo info : mSafeExternalInputs) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
- mDeviceInfos.clear();
- updateSafeDeviceInfoList();
+ mService.getHdmiCecNetwork().clearDeviceList();
}
@ServiceThreadOnly
@@ -1224,170 +1123,10 @@
&& getAvrDeviceInfo() != null;
}
- /**
- * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
- * logical address as new device info's.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- *
- * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
- * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
- * that has the same logical address as new one has.
- */
- @ServiceThreadOnly
- private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
- assertRunOnServiceThread();
- HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
- if (oldDeviceInfo != null) {
- removeDeviceInfo(deviceInfo.getId());
- }
- mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
- updateSafeDeviceInfoList();
- return oldDeviceInfo;
- }
-
- /**
- * Remove a device info corresponding to the given {@code logicalAddress}.
- * It returns removed {@link HdmiDeviceInfo} if exists.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- *
- * @param id id of device to be removed
- * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
- */
- @ServiceThreadOnly
- private HdmiDeviceInfo removeDeviceInfo(int id) {
- assertRunOnServiceThread();
- HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
- if (deviceInfo != null) {
- mDeviceInfos.remove(id);
- }
- updateSafeDeviceInfoList();
- return deviceInfo;
- }
-
- /**
- * Return a list of all {@link HdmiDeviceInfo}.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
- * does not include local device.
- */
- @ServiceThreadOnly
- List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
- assertRunOnServiceThread();
- if (includeLocalDevice) {
- return HdmiUtils.sparseArrayToList(mDeviceInfos);
- } else {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (int i = 0; i < mDeviceInfos.size(); ++i) {
- HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
- if (!isLocalDeviceAddress(info.getLogicalAddress())) {
- infoList.add(info);
- }
- }
- return infoList;
- }
- }
-
- /**
- * Return external input devices.
- */
- @GuardedBy("mLock")
- List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
- return mSafeExternalInputs;
- }
-
- @ServiceThreadOnly
- private void updateSafeDeviceInfoList() {
- assertRunOnServiceThread();
- List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
- List<HdmiDeviceInfo> externalInputs = getInputDevices();
- synchronized (mLock) {
- mSafeAllDeviceInfos = copiedDevices;
- mSafeExternalInputs = externalInputs;
- }
- }
-
- /**
- * Return a list of external cec input (source) devices.
- *
- * <p>Note that this effectively excludes non-source devices like system audio,
- * secondary TV.
- */
- private List<HdmiDeviceInfo> getInputDevices() {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (int i = 0; i < mDeviceInfos.size(); ++i) {
- HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
- if (isLocalDeviceAddress(info.getLogicalAddress())) {
- continue;
- }
- if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
- infoList.add(info);
- }
- }
- return infoList;
- }
-
- // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
- // Returns true if the policy is set to true, and the device to check does not have
- // a parent CEC device (which should be the CEC-enabled switch) in the list.
- private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
- return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
- && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
- }
-
- private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
- for (int switchPath : switches) {
- if (isParentPath(switchPath, path)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isParentPath(int parentPath, int childPath) {
- // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
- // If child's last non-zero nibble is removed, the result equals to the parent.
- for (int i = 0; i <= 12; i += 4) {
- int nibble = (childPath >> i) & 0xF;
- if (nibble != 0) {
- int parentNibble = (parentPath >> i) & 0xF;
- return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
- }
- }
- return false;
- }
-
- private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
- if (!hideDevicesBehindLegacySwitch(info)) {
- mService.invokeDeviceEventListeners(info, status);
- }
- }
-
- private boolean isLocalDeviceAddress(int address) {
- return mLocalDeviceAddresses.contains(address);
- }
-
@ServiceThreadOnly
HdmiDeviceInfo getAvrDeviceInfo() {
assertRunOnServiceThread();
- return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
- }
-
- /**
- * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
- *
- * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
- *
- * @param logicalAddress logical address of the device to be retrieved
- * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
- * Returns null if no logical address matched
- */
- @ServiceThreadOnly
- HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
- assertRunOnServiceThread();
- return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
+ return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
}
boolean hasSystemAudioDevice() {
@@ -1395,74 +1134,9 @@
}
HdmiDeviceInfo getSafeAvrDeviceInfo() {
- return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
+ return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
}
- /**
- * Thread safe version of {@link #getCecDeviceInfo(int)}.
- *
- * @param logicalAddress logical address to be retrieved
- * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
- * Returns null if no logical address matched
- */
- HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
- synchronized (mLock) {
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
- return info;
- }
- }
- return null;
- }
- }
-
- @GuardedBy("mLock")
- List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- if (isLocalDeviceAddress(info.getLogicalAddress())) {
- continue;
- }
- infoList.add(info);
- }
- return infoList;
- }
-
- /**
- * Called when a device is newly added or a new device is detected or
- * existing device is updated.
- *
- * @param info device info of a new device.
- */
- @ServiceThreadOnly
- final void addCecDevice(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- HdmiDeviceInfo old = addDeviceInfo(info);
- if (info.getLogicalAddress() == mAddress) {
- // The addition of TV device itself should not be notified.
- return;
- }
- if (old == null) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- } else if (!old.equals(info)) {
- invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- }
- }
-
- /**
- * Called when a device is removed or removal of device is detected.
- *
- * @param address a logical address of a device to be removed
- */
- @ServiceThreadOnly
- final void removeCecDevice(int address) {
- assertRunOnServiceThread();
- HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
- mCecMessageCache.flushMessagesFrom(address);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
@ServiceThreadOnly
void handleRemoveActiveRoutingPath(int path) {
@@ -1501,72 +1175,10 @@
}
}
- /**
- * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
- * the given routing path. CEC devices use routing path for its physical address to
- * describe the hierarchy of the devices in the network.
- *
- * @param path routing path or physical address
- * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
- */
- @ServiceThreadOnly
- final HdmiDeviceInfo getDeviceInfoByPath(int path) {
- assertRunOnServiceThread();
- for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
- if (info.getPhysicalAddress() == path) {
- return info;
- }
- }
- return null;
- }
-
- /**
- * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
- * the given routing path. This is the version accessible safely from threads
- * other than service thread.
- *
- * @param path routing path or physical address
- * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
- */
- HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
- synchronized (mLock) {
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- if (info.getPhysicalAddress() == path) {
- return info;
- }
- }
- return null;
- }
- }
-
- /**
- * Whether a device of the specified physical address and logical address exists
- * in a device info list. However, both are minimal condition and it could
- * be different device from the original one.
- *
- * @param logicalAddress logical address of a device to be searched
- * @param physicalAddress physical address of a device to be searched
- * @return true if exist; otherwise false
- */
- @ServiceThreadOnly
- boolean isInDeviceList(int logicalAddress, int physicalAddress) {
- assertRunOnServiceThread();
- HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
- if (device == null) {
- return false;
- }
- return device.getPhysicalAddress() == physicalAddress;
- }
-
@Override
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
-
- if (!connected) {
- removeCecSwitches(portId);
- }
-
// Turning System Audio Mode off when the AVR is unlugged or standby.
// When the device is not unplugged but reawaken from standby, we check if the System
// Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
@@ -1588,16 +1200,6 @@
}
}
- private void removeCecSwitches(int portId) {
- Iterator<Integer> it = mCecSwitches.iterator();
- while (!it.hasNext()) {
- int path = it.next();
- if (pathToPortId(path) == portId) {
- it.remove();
- }
- }
- }
-
@Override
@ServiceThreadOnly
void setAutoDeviceOff(boolean enabled) {
@@ -1765,7 +1367,7 @@
}
private boolean checkRecorder(int recorderAddress) {
- HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
+ HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress);
return (device != null)
&& (HdmiUtils.getTypeFromAddress(recorderAddress)
== HdmiDeviceInfo.DEVICE_RECORDER);
@@ -1871,24 +1473,6 @@
});
}
- void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
- if (info == null) {
- Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
- return;
- }
-
- if (info.getDevicePowerStatus() == newPowerStatus) {
- return;
- }
-
- HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
- // addDeviceInfo replaces old device info with new one if exists.
- addDeviceInfo(newInfo);
-
- invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
- }
-
@Override
protected boolean handleMenuStatus(HdmiCecMessage message) {
// Do nothing and just return true not to prevent from responding <Feature Abort>.
@@ -1897,7 +1481,7 @@
@Override
protected void sendStandby(int deviceId) {
- HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
+ HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId);
if (targetDevice == null) {
return;
}
@@ -1934,11 +1518,5 @@
pw.println("mAutoWakeup: " + mAutoWakeup);
pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
pw.println("mPrevPortId: " + mPrevPortId);
- pw.println("CEC devices:");
- pw.increaseIndent();
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- pw.println(info);
- }
- pw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 28bd97e..d4593af 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -128,7 +128,7 @@
FixedLengthValidator oneByteValidator = new FixedLengthValidator(1);
addValidationInfo(Constants.MESSAGE_CEC_VERSION, oneByteValidator, DEST_DIRECT);
addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
- new FixedLengthValidator(3), DEST_BROADCAST);
+ new AsciiValidator(3), DEST_BROADCAST);
// TODO: Handle messages for the Deck Control.
@@ -148,8 +148,8 @@
maxLengthValidator, DEST_ALL | SRC_UNREGISTERED);
// Messages for the OSD.
- addValidationInfo(Constants.MESSAGE_SET_OSD_STRING, maxLengthValidator, DEST_DIRECT);
- addValidationInfo(Constants.MESSAGE_SET_OSD_NAME, maxLengthValidator, DEST_DIRECT);
+ addValidationInfo(Constants.MESSAGE_SET_OSD_STRING, new OsdStringValidator(), DEST_DIRECT);
+ addValidationInfo(Constants.MESSAGE_SET_OSD_NAME, new AsciiValidator(1, 14), DEST_DIRECT);
// Messages for the Device Menu Control.
addValidationInfo(Constants.MESSAGE_MENU_REQUEST, oneByteValidator, DEST_DIRECT);
@@ -299,6 +299,37 @@
return (value >= min && value <= max);
}
+ /**
+ * Check if the given value is a valid Display Control. A valid value is one which falls within
+ * the range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17)
+ *
+ * @param value Display Control
+ * @return true if the Display Control is valid
+ */
+ private boolean isValidDisplayControl(int value) {
+ value = value & 0xFF;
+ return (value == 0x00 || value == 0x40 || value == 0x80 || value == 0xC0);
+ }
+
+ /**
+ * Check if the given params has valid ASCII characters.
+ * A valid ASCII character is a printable character. It should fall within range description
+ * defined in CEC 1.4 Specification : Operand Descriptions (Section 17)
+ *
+ * @param params parameter consisting of string
+ * @param offset Start offset of string
+ * @param maxLength Maximum length of string to be evaluated
+ * @return true if the given type is valid
+ */
+ private boolean isValidAsciiString(byte[] params, int offset, int maxLength) {
+ for (int i = offset; i < params.length && i < maxLength; i++) {
+ if (!isWithinRange(params[i], 0x20, 0x7E)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private class PhysicalAddressValidator implements ParameterValidator {
@Override
public int isValid(byte[] params) {
@@ -359,4 +390,55 @@
|| params[0] == 0x1F);
}
}
+
+ /**
+ * Check if the given parameters represents printable characters.
+ * A valid parameter should lie within the range description of ASCII defined in CEC 1.4
+ * Specification : Operand Descriptions (Section 17)
+ */
+ private class AsciiValidator implements ParameterValidator {
+ private final int mMinLength;
+ private final int mMaxLength;
+
+ AsciiValidator(int length) {
+ mMinLength = length;
+ mMaxLength = length;
+ }
+
+ AsciiValidator(int minLength, int maxLength) {
+ mMinLength = minLength;
+ mMaxLength = maxLength;
+ }
+
+ @Override
+ public int isValid(byte[] params) {
+ // If the length is longer than expected, we assume it's OK since the parameter can be
+ // extended in the future version.
+ if (params.length < mMinLength) {
+ return ERROR_PARAMETER_SHORT;
+ }
+ return toErrorCode(isValidAsciiString(params, 0, mMaxLength));
+ }
+ }
+
+ /**
+ * Check if the given parameters is valid OSD String.
+ * A valid parameter should lie within the range description of ASCII defined in CEC 1.4
+ * Specification : Operand Descriptions (Section 17)
+ */
+ private class OsdStringValidator implements ParameterValidator {
+ @Override
+ public int isValid(byte[] params) {
+ // If the length is longer than expected, we assume it's OK since the parameter can be
+ // extended in the future version.
+ if (params.length < 2) {
+ return ERROR_PARAMETER_SHORT;
+ }
+ return toErrorCode(
+ // Display Control
+ isValidDisplayControl(params[0])
+ // OSD String
+ && isValidAsciiString(params, 1, 14));
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
new file mode 100644
index 0000000..5d75a63
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -0,0 +1,846 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import android.annotation.Nullable;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * Holds information about the current state of the HDMI CEC network. It is the sole source of
+ * truth for device information in the CEC network.
+ *
+ * This information includes:
+ * - All local devices
+ * - All HDMI ports, their capabilities and status
+ * - All devices connected to the CEC bus
+ *
+ * This class receives all incoming CEC messages and passively listens to device updates to fill
+ * out the above information.
+ * This class should not take any active action in sending CEC messages.
+ *
+ * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD
+ * names, power states can be outdated.
+ */
+class HdmiCecNetwork {
+ private static final String TAG = "HdmiCecNetwork";
+
+ protected final Object mLock;
+ private final HdmiControlService mHdmiControlService;
+ private final HdmiCecController mHdmiCecController;
+ private final HdmiMhlControllerStub mHdmiMhlController;
+ private final Handler mHandler;
+ // Stores the local CEC devices in the system. Device type is used for key.
+ private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
+
+ // Map-like container of all cec devices including local ones.
+ // device id is used as key of container.
+ // This is not thread-safe. For external purpose use mSafeDeviceInfos.
+ private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
+ // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
+ // other CEC devices since they might not have logical address.
+ private final ArraySet<Integer> mCecSwitches = new ArraySet<>();
+ // Copy of mDeviceInfos to guarantee thread-safety.
+ @GuardedBy("mLock")
+ private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
+ // All external cec input(source) devices. Does not include system audio device.
+ @GuardedBy("mLock")
+ private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
+ // HDMI port information. Stored in the unmodifiable list to keep the static information
+ // from being modified.
+ @GuardedBy("mLock")
+ private List<HdmiPortInfo> mPortInfo = Collections.emptyList();
+
+ // Map from path(physical address) to port ID.
+ private UnmodifiableSparseIntArray mPortIdMap;
+
+ // Map from port ID to HdmiPortInfo.
+ private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
+
+ // Map from port ID to HdmiDeviceInfo.
+ private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
+
+ HdmiCecNetwork(HdmiControlService hdmiControlService,
+ HdmiCecController hdmiCecController,
+ HdmiMhlControllerStub hdmiMhlController) {
+ mHdmiControlService = hdmiControlService;
+ mHdmiCecController = hdmiCecController;
+ mHdmiMhlController = hdmiMhlController;
+ mHandler = new Handler(mHdmiControlService.getServiceLooper());
+ mLock = mHdmiControlService.getServiceLock();
+ }
+
+ private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
+ for (int switchPath : switches) {
+ if (isParentPath(switchPath, path)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isParentPath(int parentPath, int childPath) {
+ // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
+ // If child's last non-zero nibble is removed, the result equals to the parent.
+ for (int i = 0; i <= 12; i += 4) {
+ int nibble = (childPath >> i) & 0xF;
+ if (nibble != 0) {
+ int parentNibble = (parentPath >> i) & 0xF;
+ return parentNibble == 0 && (childPath >> i + 4) == (parentPath >> i + 4);
+ }
+ }
+ return false;
+ }
+
+ public void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
+ mLocalDevices.put(deviceType, device);
+ }
+
+ /**
+ * Return the locally hosted logical device of a given type.
+ *
+ * @param deviceType logical device type
+ * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
+ * otherwise null.
+ */
+ HdmiCecLocalDevice getLocalDevice(int deviceType) {
+ return mLocalDevices.get(deviceType);
+ }
+
+ /**
+ * Return a list of all {@link HdmiCecLocalDevice}s.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ @ServiceThreadOnly
+ List<HdmiCecLocalDevice> getLocalDeviceList() {
+ assertRunOnServiceThread();
+ return HdmiUtils.sparseArrayToList(mLocalDevices);
+ }
+
+ @ServiceThreadOnly
+ boolean isAllocatedLocalDeviceAddress(int address) {
+ assertRunOnServiceThread();
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ if (mLocalDevices.valueAt(i).isAddressOf(address)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Clear all logical addresses registered in the device.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ @ServiceThreadOnly
+ void clearLogicalAddress() {
+ assertRunOnServiceThread();
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ mLocalDevices.valueAt(i).clearAddress();
+ }
+ }
+
+ @ServiceThreadOnly
+ void clearLocalDevices() {
+ assertRunOnServiceThread();
+ mLocalDevices.clear();
+ }
+
+ public HdmiDeviceInfo getDeviceInfo(int id) {
+ return mDeviceInfos.get(id);
+ }
+
+ /**
+ * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
+ * logical address as new device info's.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
+ * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
+ * that has the same logical address as new one has.
+ */
+ @ServiceThreadOnly
+ private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
+ mHdmiControlService.checkLogicalAddressConflictAndReallocate(
+ deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress());
+ if (oldDeviceInfo != null) {
+ removeDeviceInfo(deviceInfo.getId());
+ }
+ mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
+ updateSafeDeviceInfoList();
+ return oldDeviceInfo;
+ }
+
+ /**
+ * Remove a device info corresponding to the given {@code logicalAddress}.
+ * It returns removed {@link HdmiDeviceInfo} if exists.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param id id of device to be removed
+ * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
+ */
+ @ServiceThreadOnly
+ private HdmiDeviceInfo removeDeviceInfo(int id) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
+ if (deviceInfo != null) {
+ mDeviceInfos.remove(id);
+ }
+ updateSafeDeviceInfoList();
+ return deviceInfo;
+ }
+
+ /**
+ * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
+ *
+ * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
+ *
+ * @param logicalAddress logical address of the device to be retrieved
+ * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
+ * Returns null if no logical address matched
+ */
+ @ServiceThreadOnly
+ @Nullable
+ HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
+ assertRunOnServiceThread();
+ return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
+ }
+
+ /**
+ * Called when a device is newly added or a new device is detected or
+ * existing device is updated.
+ *
+ * @param info device info of a new device.
+ */
+ @ServiceThreadOnly
+ final void addCecDevice(HdmiDeviceInfo info) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo old = addDeviceInfo(info);
+ if (isLocalDeviceAddress(info.getLogicalAddress())) {
+ // The addition of a local device should not notify listeners
+ return;
+ }
+ if (old == null) {
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ } else if (!old.equals(info)) {
+ invokeDeviceEventListener(old,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+ }
+
+ private void invokeDeviceEventListener(HdmiDeviceInfo info, int event) {
+ if (!hideDevicesBehindLegacySwitch(info)) {
+ mHdmiControlService.invokeDeviceEventListeners(info, event);
+ }
+ }
+
+ /**
+ * Called when a device is updated.
+ *
+ * @param info device info of the updating device.
+ */
+ @ServiceThreadOnly
+ final void updateCecDevice(HdmiDeviceInfo info) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo old = addDeviceInfo(info);
+
+ if (old == null) {
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ } else if (!old.equals(info)) {
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+ }
+
+ @ServiceThreadOnly
+ private void updateSafeDeviceInfoList() {
+ assertRunOnServiceThread();
+ List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
+ List<HdmiDeviceInfo> externalInputs = getInputDevices();
+ mSafeAllDeviceInfos = copiedDevices;
+ mSafeExternalInputs = externalInputs;
+ }
+
+ /**
+ * Return a list of all {@link HdmiDeviceInfo}.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
+ * does not include local device.
+ */
+ @ServiceThreadOnly
+ List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
+ assertRunOnServiceThread();
+ if (includeLocalDevice) {
+ return HdmiUtils.sparseArrayToList(mDeviceInfos);
+ } else {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); ++i) {
+ HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
+ if (!isLocalDeviceAddress(info.getLogicalAddress())) {
+ infoList.add(info);
+ }
+ }
+ return infoList;
+ }
+ }
+
+ /**
+ * Return external input devices.
+ */
+ @GuardedBy("mLock")
+ List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
+ return mSafeExternalInputs;
+ }
+
+ /**
+ * Return a list of external cec input (source) devices.
+ *
+ * <p>Note that this effectively excludes non-source devices like system audio,
+ * secondary TV.
+ */
+ private List<HdmiDeviceInfo> getInputDevices() {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); ++i) {
+ HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
+ if (isLocalDeviceAddress(info.getLogicalAddress())) {
+ continue;
+ }
+ if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
+ infoList.add(info);
+ }
+ }
+ return infoList;
+ }
+
+ // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
+ // This only applies to TV devices.
+ // Returns true if the policy is set to true, and the device to check does not have
+ // a parent CEC device (which should be the CEC-enabled switch) in the list.
+ private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
+ return isLocalDeviceAddress(Constants.ADDR_TV)
+ && HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
+ && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches());
+ }
+
+ /**
+ * Called when a device is removed or removal of device is detected.
+ *
+ * @param address a logical address of a device to be removed
+ */
+ @ServiceThreadOnly
+ final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
+
+ localDevice.mCecMessageCache.flushMessagesFrom(address);
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+
+ public void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+ HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ if (info == null) {
+ Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
+ return;
+ }
+
+ if (info.getDevicePowerStatus() == newPowerStatus) {
+ return;
+ }
+
+ updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus));
+ }
+
+ /**
+ * Whether a device of the specified physical address is connected to ARC enabled port.
+ */
+ boolean isConnectedToArcPort(int physicalAddress) {
+ int portId = physicalAddressToPortId(physicalAddress);
+ if (portId != Constants.INVALID_PORT_ID) {
+ return mPortInfoMap.get(portId).isArcSupported();
+ }
+ return false;
+ }
+
+
+ // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
+ // keep them in one place.
+ @ServiceThreadOnly
+ @VisibleForTesting
+ public void initPortInfo() {
+ assertRunOnServiceThread();
+ HdmiPortInfo[] cecPortInfo = null;
+ // CEC HAL provides majority of the info while MHL does only MHL support flag for
+ // each port. Return empty array if CEC HAL didn't provide the info.
+ if (mHdmiCecController != null) {
+ cecPortInfo = mHdmiCecController.getPortInfos();
+ }
+ if (cecPortInfo == null) {
+ return;
+ }
+
+ SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
+ SparseIntArray portIdMap = new SparseIntArray();
+ SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
+ for (HdmiPortInfo info : cecPortInfo) {
+ portIdMap.put(info.getAddress(), info.getId());
+ portInfoMap.put(info.getId(), info);
+ portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
+ }
+ mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
+ mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
+ mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
+
+ if (mHdmiMhlController == null) {
+ return;
+ }
+ HdmiPortInfo[] mhlPortInfo = mHdmiMhlController.getPortInfos();
+ ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
+ for (HdmiPortInfo info : mhlPortInfo) {
+ if (info.isMhlSupported()) {
+ mhlSupportedPorts.add(info.getId());
+ }
+ }
+
+ // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
+ // cec port info if we do not have have port that supports MHL.
+ if (mhlSupportedPorts.isEmpty()) {
+ setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
+ return;
+ }
+ ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
+ for (HdmiPortInfo info : cecPortInfo) {
+ if (mhlSupportedPorts.contains(info.getId())) {
+ result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
+ info.isCecSupported(), true, info.isArcSupported()));
+ } else {
+ result.add(info);
+ }
+ }
+ setPortInfo(Collections.unmodifiableList(result));
+ }
+
+ HdmiDeviceInfo getDeviceForPortId(int portId) {
+ return mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
+ }
+
+ /**
+ * Whether a device of the specified physical address and logical address exists
+ * in a device info list. However, both are minimal condition and it could
+ * be different device from the original one.
+ *
+ * @param logicalAddress logical address of a device to be searched
+ * @param physicalAddress physical address of a device to be searched
+ * @return true if exist; otherwise false
+ */
+ @ServiceThreadOnly
+ boolean isInDeviceList(int logicalAddress, int physicalAddress) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
+ if (device == null) {
+ return false;
+ }
+ return device.getPhysicalAddress() == physicalAddress;
+ }
+
+ /**
+ * Passively listen to incoming CEC messages.
+ *
+ * This shall not result in any CEC messages being sent.
+ */
+ @ServiceThreadOnly
+ public void handleCecMessage(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Add device by logical address if it's not already known
+ int sourceAddress = message.getSource();
+ if (getCecDeviceInfo(sourceAddress) == null) {
+ HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress,
+ HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID,
+ HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID,
+ HdmiUtils.getDefaultDeviceName(sourceAddress));
+ addCecDevice(newDevice);
+ }
+
+ switch (message.getOpcode()) {
+ case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ handleReportPhysicalAddress(message);
+ break;
+ case Constants.MESSAGE_REPORT_POWER_STATUS:
+ handleReportPowerStatus(message);
+ break;
+ case Constants.MESSAGE_SET_OSD_NAME:
+ handleSetOsdName(message);
+ break;
+ case Constants.MESSAGE_DEVICE_VENDOR_ID:
+ handleDeviceVendorId(message);
+ break;
+
+ }
+ }
+
+ @ServiceThreadOnly
+ private void handleReportPhysicalAddress(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ int type = message.getParams()[2];
+
+ if (updateCecSwitchInfo(logicalAddress, type, physicalAddress)) return;
+
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress);
+ if (deviceInfo == null) {
+ Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message);
+ } else {
+ HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ physicalAddress,
+ physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(),
+ deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus());
+ updateCecDevice(updatedDeviceInfo);
+ }
+ }
+
+ @ServiceThreadOnly
+ private void handleReportPowerStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Update power status of device
+ int newStatus = message.getParams()[0] & 0xFF;
+ updateDevicePowerStatus(message.getSource(), newStatus);
+ }
+
+ @ServiceThreadOnly
+ private void handleSetOsdName(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ String osdName;
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress);
+ // If the device is not in device list, ignore it.
+ if (deviceInfo == null) {
+ Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
+ return;
+ }
+ try {
+ osdName = new String(message.getParams(), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
+ return;
+ }
+
+ if (deviceInfo.getDisplayName() != null
+ && deviceInfo.getDisplayName().equals(osdName)) {
+ Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
+ return;
+ }
+
+ Slog.d(TAG, "Updating device OSD name from "
+ + deviceInfo.getDisplayName()
+ + " to " + osdName);
+ updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
+ deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName,
+ deviceInfo.getDevicePowerStatus()));
+ }
+
+ @ServiceThreadOnly
+ private void handleDeviceVendorId(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ int vendorId = HdmiUtils.threeBytesToInt(message.getParams());
+
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress);
+ if (deviceInfo == null) {
+ Slog.i(TAG, "Unknown source device info for <Device Vendor ID> " + message);
+ } else {
+ HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ deviceInfo.getPhysicalAddress(),
+ deviceInfo.getPortId(), deviceInfo.getDeviceType(), vendorId,
+ deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus());
+ updateCecDevice(updatedDeviceInfo);
+ }
+ }
+
+ void addCecSwitch(int physicalAddress) {
+ mCecSwitches.add(physicalAddress);
+ }
+
+ public ArraySet<Integer> getCecSwitches() {
+ return mCecSwitches;
+ }
+
+ void removeDevicesConnectedToPort(int portId) {
+ Iterator<Integer> it = mCecSwitches.iterator();
+ while (it.hasNext()) {
+ int path = it.next();
+ int devicePortId = physicalAddressToPortId(path);
+ if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) {
+ it.remove();
+ }
+ }
+ List<Integer> toRemove = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); i++) {
+ int key = mDeviceInfos.keyAt(i);
+ int physicalAddress = mDeviceInfos.get(key).getPhysicalAddress();
+ int devicePortId = physicalAddressToPortId(physicalAddress);
+ if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) {
+ toRemove.add(key);
+ }
+ }
+ for (Integer key : toRemove) {
+ removeDeviceInfo(key);
+ }
+ }
+
+ boolean updateCecSwitchInfo(int address, int type, int path) {
+ if (address == Constants.ADDR_UNREGISTERED
+ && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
+ mCecSwitches.add(path);
+ updateSafeDeviceInfoList();
+ return true; // Pure switch does not need further processing. Return here.
+ }
+ if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
+ mCecSwitches.add(path);
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ if (isLocalDeviceAddress(info.getLogicalAddress())) {
+ continue;
+ }
+ infoList.add(info);
+ }
+ return infoList;
+ }
+
+ /**
+ * Thread safe version of {@link #getCecDeviceInfo(int)}.
+ *
+ * @param logicalAddress logical address to be retrieved
+ * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
+ * Returns null if no logical address matched
+ */
+ HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
+ *
+ *
+ *
+ * qq * the given routing path. CEC devices use routing path for its physical address to
+ * describe the hierarchy of the devices in the network.
+ *
+ * @param path routing path or physical address
+ * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
+ */
+ @ServiceThreadOnly
+ final HdmiDeviceInfo getDeviceInfoByPath(int path) {
+ assertRunOnServiceThread();
+ for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
+ if (info.getPhysicalAddress() == path) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
+ * the given routing path. This is the version accessible safely from threads
+ * other than service thread.
+ *
+ * @param path routing path or physical address
+ * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
+ */
+ HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ if (info.getPhysicalAddress() == path) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public int getPhysicalAddress() {
+ return mHdmiCecController.getPhysicalAddress();
+ }
+
+ @ServiceThreadOnly
+ public void clear() {
+ assertRunOnServiceThread();
+ initPortInfo();
+ clearDeviceList();
+ clearLocalDevices();
+ }
+
+ @ServiceThreadOnly
+ public void clearDeviceList() {
+ assertRunOnServiceThread();
+ for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
+ if (info.getPhysicalAddress() == getPhysicalAddress()) {
+ continue;
+ }
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+ mDeviceInfos.clear();
+ updateSafeDeviceInfoList();
+ }
+
+ /**
+ * Returns HDMI port information for the given port id.
+ *
+ * @param portId HDMI port id
+ * @return {@link HdmiPortInfo} for the given port
+ */
+ HdmiPortInfo getPortInfo(int portId) {
+ return mPortInfoMap.get(portId, null);
+ }
+
+ /**
+ * Returns the routing path (physical address) of the HDMI port for the given
+ * port id.
+ */
+ int portIdToPath(int portId) {
+ HdmiPortInfo portInfo = getPortInfo(portId);
+ if (portInfo == null) {
+ Slog.e(TAG, "Cannot find the port info: " + portId);
+ return Constants.INVALID_PHYSICAL_ADDRESS;
+ }
+ return portInfo.getAddress();
+ }
+
+ /**
+ * Returns the id of HDMI port located at the current device that runs this method.
+ *
+ * For TV with physical address 0x0000, target device 0x1120, we want port physical address
+ * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
+ * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
+ *
+ * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
+ *
+ * @param path the target device's physical address.
+ * @return the id of the port that the target device eventually connects to
+ * on the current device.
+ */
+ int physicalAddressToPortId(int path) {
+ int mask = 0xF000;
+ int finalMask = 0xF000;
+ int physicalAddress;
+ physicalAddress = getPhysicalAddress();
+ int maskedAddress = physicalAddress;
+
+ while (maskedAddress != 0) {
+ maskedAddress = physicalAddress & mask;
+ finalMask |= mask;
+ mask >>= 4;
+ }
+
+ int portAddress = path & finalMask;
+ return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
+ }
+
+ List<HdmiPortInfo> getPortInfo() {
+ return mPortInfo;
+ }
+
+ void setPortInfo(List<HdmiPortInfo> portInfo) {
+ mPortInfo = portInfo;
+ }
+
+ private boolean isLocalDeviceAddress(int address) {
+ for (int i = 0; i < mLocalDevices.size(); i++) {
+ int key = mLocalDevices.keyAt(i);
+ if (mLocalDevices.get(key).mAddress == address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void assertRunOnServiceThread() {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ throw new IllegalStateException("Should run on service thread.");
+ }
+ }
+
+ protected void dump(IndentingPrintWriter pw) {
+ pw.println("HDMI CEC Network");
+ pw.increaseIndent();
+ HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
+ pw.increaseIndent();
+ mLocalDevices.valueAt(i).dump(pw);
+
+ pw.println("Active Source history:");
+ pw.increaseIndent();
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ ArrayBlockingQueue<HdmiCecController.Dumpable> activeSourceHistory =
+ mLocalDevices.valueAt(i).getActiveSourceHistory();
+ for (HdmiCecController.Dumpable activeSourceEvent : activeSourceHistory) {
+ activeSourceEvent.dump(pw, sdf);
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ HdmiUtils.dumpIterable(pw, "mDeviceInfos:", mSafeAllDeviceInfos);
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8bb89da..ee86593 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -71,10 +71,8 @@
import android.provider.Settings.Global;
import android.sysprop.HdmiProperties;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -91,7 +89,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -177,6 +174,8 @@
static final int STANDBY_SCREEN_OFF = 0;
static final int STANDBY_SHUTDOWN = 1;
+ private HdmiCecNetwork mHdmiCecNetwork;
+
// Logical address of the active source.
@GuardedBy("mLock")
protected final ActiveSource mActiveSource = new ActiveSource();
@@ -333,21 +332,6 @@
@Nullable
private HdmiCecController mCecController;
- // HDMI port information. Stored in the unmodifiable list to keep the static information
- // from being modified.
- // This variable is null if the current device does not have hdmi input.
- @GuardedBy("mLock")
- private List<HdmiPortInfo> mPortInfo = null;
-
- // Map from path(physical address) to port ID.
- private UnmodifiableSparseIntArray mPortIdMap;
-
- // Map from port ID to HdmiPortInfo.
- private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
-
- // Map from port ID to HdmiDeviceInfo.
- private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
-
private HdmiCecMessageValidator mMessageValidator;
@ServiceThreadOnly
@@ -389,10 +373,6 @@
@Nullable
private Looper mIoLooper;
- // Thread safe physical address
- @GuardedBy("mLock")
- private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
-
// Last input port before switching to the MHL port. Should switch back to this port
// when the mobile device sends the request one touch play with off.
// Gets invalidated if we go to other port/input.
@@ -507,42 +487,7 @@
@Override
public void onStart() {
- if (mIoLooper == null) {
- mIoThread.start();
- mIoLooper = mIoThread.getLooper();
- }
- mPowerStatus = getInitialPowerStatus();
- mProhibitMode = false;
- mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
- mHdmiCecVolumeControlEnabled = readBooleanSetting(
- Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
- mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
-
- if (mCecController == null) {
- mCecController = HdmiCecController.create(this, getAtomWriter());
- }
- if (mCecController != null) {
- if (mHdmiControlEnabled) {
- initializeCec(INITIATED_BY_BOOT_UP);
- } else {
- mCecController.setOption(OptionKey.ENABLE_CEC, false);
- }
- } else {
- Slog.i(TAG, "Device does not support HDMI-CEC.");
- return;
- }
- if (mMhlController == null) {
- mMhlController = HdmiMhlControllerStub.create(this);
- }
- if (!mMhlController.isReady()) {
- Slog.i(TAG, "Device does not support MHL-control.");
- }
- mMhlDevices = Collections.emptyList();
-
- initPortInfo();
- if (mMessageValidator == null) {
- mMessageValidator = new HdmiCecMessageValidator(this);
- }
+ initService();
publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
if (mCecController != null) {
@@ -560,6 +505,46 @@
mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
}
+ @VisibleForTesting
+ void initService() {
+ if (mIoLooper == null) {
+ mIoThread.start();
+ mIoLooper = mIoThread.getLooper();
+ }
+ mPowerStatus = getInitialPowerStatus();
+ mProhibitMode = false;
+ mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
+ mHdmiCecVolumeControlEnabled = readBooleanSetting(
+ Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
+ mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
+
+ if (mCecController == null) {
+ mCecController = HdmiCecController.create(this, getAtomWriter());
+ }
+ if (mCecController == null) {
+ Slog.i(TAG, "Device does not support HDMI-CEC.");
+ return;
+ }
+ if (mMhlController == null) {
+ mMhlController = HdmiMhlControllerStub.create(this);
+ }
+ if (!mMhlController.isReady()) {
+ Slog.i(TAG, "Device does not support MHL-control.");
+ }
+ mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
+ if (mHdmiControlEnabled) {
+ initializeCec(INITIATED_BY_BOOT_UP);
+ } else {
+ mCecController.setOption(OptionKey.ENABLE_CEC, false);
+ }
+ mMhlDevices = Collections.emptyList();
+
+ mHdmiCecNetwork.initPortInfo();
+ if (mMessageValidator == null) {
+ mMessageValidator = new HdmiCecMessageValidator(this);
+ }
+ }
+
private void bootCompleted() {
// on boot, if device is interactive, set HDMI CEC state as powered on as well
if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) {
@@ -588,6 +573,15 @@
}
@VisibleForTesting
+ void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) {
+ mHdmiCecNetwork = hdmiCecNetwork;
+ }
+
+ public HdmiCecNetwork getHdmiCecNetwork() {
+ return mHdmiCecNetwork;
+ }
+
+ @VisibleForTesting
void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
mMhlController = hdmiMhlController;
}
@@ -705,7 +699,7 @@
break;
case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
for (int type : mLocalDevices) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice != null) {
localDevice.setAutoDeviceOff(enabled);
}
@@ -800,7 +794,7 @@
// A container for [Device type, Local device info].
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
}
@@ -832,43 +826,48 @@
for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
mCecController.allocateLogicalAddress(localDevice.getType(),
localDevice.getPreferredAddress(), new AllocateAddressCallback() {
- @Override
- public void onAllocated(int deviceType, int logicalAddress) {
- if (logicalAddress == Constants.ADDR_UNREGISTERED) {
- Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
- } else {
- // Set POWER_STATUS_ON to all local devices because they share lifetime
- // with system.
- HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
- HdmiControlManager.POWER_STATUS_ON);
- localDevice.setDeviceInfo(deviceInfo);
- mCecController.addLocalDevice(deviceType, localDevice);
- mCecController.addLogicalAddress(logicalAddress);
- allocatedDevices.add(localDevice);
- }
+ @Override
+ public void onAllocated(int deviceType, int logicalAddress) {
+ if (logicalAddress == Constants.ADDR_UNREGISTERED) {
+ Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType
+ + "]");
+ } else {
+ // Set POWER_STATUS_ON to all local devices because they share
+ // lifetime
+ // with system.
+ HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress,
+ deviceType,
+ HdmiControlManager.POWER_STATUS_ON);
+ localDevice.setDeviceInfo(deviceInfo);
+ mHdmiCecNetwork.addLocalDevice(deviceType, localDevice);
+ mCecController.addLogicalAddress(logicalAddress);
+ allocatedDevices.add(localDevice);
+ }
- // Address allocation completed for all devices. Notify each device.
- if (allocatingDevices.size() == ++finished[0]) {
- mAddressAllocated = true;
- if (initiatedBy != INITIATED_BY_HOTPLUG) {
- // In case of the hotplug we don't call onInitializeCecComplete()
- // since we reallocate the logical address only.
- onInitializeCecComplete(initiatedBy);
+ // Address allocation completed for all devices. Notify each device.
+ if (allocatingDevices.size() == ++finished[0]) {
+ mAddressAllocated = true;
+ if (initiatedBy != INITIATED_BY_HOTPLUG) {
+ // In case of the hotplug we don't call
+ // onInitializeCecComplete()
+ // since we reallocate the logical address only.
+ onInitializeCecComplete(initiatedBy);
+ }
+ notifyAddressAllocated(allocatedDevices, initiatedBy);
+ // Reinvoke the saved display status callback once the local
+ // device is ready.
+ if (mDisplayStatusCallback != null) {
+ queryDisplayStatus(mDisplayStatusCallback);
+ mDisplayStatusCallback = null;
+ }
+ if (mOtpCallbackPendingAddressAllocation != null) {
+ oneTouchPlay(mOtpCallbackPendingAddressAllocation);
+ mOtpCallbackPendingAddressAllocation = null;
+ }
+ mCecMessageBuffer.processMessages();
+ }
}
- notifyAddressAllocated(allocatedDevices, initiatedBy);
- // Reinvoke the saved display status callback once the local device is ready.
- if (mDisplayStatusCallback != null) {
- queryDisplayStatus(mDisplayStatusCallback);
- mDisplayStatusCallback = null;
- }
- if (mOtpCallbackPendingAddressAllocation != null) {
- oneTouchPlay(mOtpCallbackPendingAddressAllocation);
- mOtpCallbackPendingAddressAllocation = null;
- }
- mCecMessageBuffer.processMessages();
- }
- }
- });
+ });
}
}
@@ -888,88 +887,14 @@
return mAddressAllocated;
}
- // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
- // keep them in one place.
- @ServiceThreadOnly
- @VisibleForTesting
- protected void initPortInfo() {
- assertRunOnServiceThread();
- HdmiPortInfo[] cecPortInfo = null;
-
- synchronized (mLock) {
- mPhysicalAddress = getPhysicalAddress();
- }
-
- // CEC HAL provides majority of the info while MHL does only MHL support flag for
- // each port. Return empty array if CEC HAL didn't provide the info.
- if (mCecController != null) {
- cecPortInfo = mCecController.getPortInfos();
- }
- if (cecPortInfo == null) {
- return;
- }
-
- SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
- SparseIntArray portIdMap = new SparseIntArray();
- SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
- for (HdmiPortInfo info : cecPortInfo) {
- portIdMap.put(info.getAddress(), info.getId());
- portInfoMap.put(info.getId(), info);
- portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
- }
- mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
- mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
- mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
-
- if (mMhlController == null) {
- return;
- }
- HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
- ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
- for (HdmiPortInfo info : mhlPortInfo) {
- if (info.isMhlSupported()) {
- mhlSupportedPorts.add(info.getId());
- }
- }
-
- // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
- // cec port info if we do not have have port that supports MHL.
- if (mhlSupportedPorts.isEmpty()) {
- setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
- return;
- }
- ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
- for (HdmiPortInfo info : cecPortInfo) {
- if (mhlSupportedPorts.contains(info.getId())) {
- result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
- info.isCecSupported(), true, info.isArcSupported()));
- } else {
- result.add(info);
- }
- }
- setPortInfo(Collections.unmodifiableList(result));
- }
-
List<HdmiPortInfo> getPortInfo() {
synchronized (mLock) {
- return mPortInfo;
+ return mHdmiCecNetwork.getPortInfo();
}
}
- void setPortInfo(List<HdmiPortInfo> portInfo) {
- synchronized (mLock) {
- mPortInfo = portInfo;
- }
- }
-
- /**
- * Returns HDMI port information for the given port id.
- *
- * @param portId HDMI port id
- * @return {@link HdmiPortInfo} for the given port
- */
HdmiPortInfo getPortInfo(int portId) {
- return mPortInfoMap.get(portId, null);
+ return mHdmiCecNetwork.getPortInfo(portId);
}
/**
@@ -977,12 +902,7 @@
* port id.
*/
int portIdToPath(int portId) {
- HdmiPortInfo portInfo = getPortInfo(portId);
- if (portInfo == null) {
- Slog.e(TAG, "Cannot find the port info: " + portId);
- return Constants.INVALID_PHYSICAL_ADDRESS;
- }
- return portInfo.getAddress();
+ return mHdmiCecNetwork.portIdToPath(portId);
}
/**
@@ -999,26 +919,11 @@
* on the current device.
*/
int pathToPortId(int path) {
- int mask = 0xF000;
- int finalMask = 0xF000;
- int physicalAddress;
- synchronized (mLock) {
- physicalAddress = mPhysicalAddress;
- }
- int maskedAddress = physicalAddress;
-
- while (maskedAddress != 0) {
- maskedAddress = physicalAddress & mask;
- finalMask |= mask;
- mask >>= 4;
- }
-
- int portAddress = path & finalMask;
- return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
+ return mHdmiCecNetwork.physicalAddressToPortId(path);
}
boolean isValidPortId(int portId) {
- return getPortInfo(portId) != null;
+ return mHdmiCecNetwork.getPortInfo(portId) != null;
}
/**
@@ -1068,7 +973,7 @@
@ServiceThreadOnly
HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
assertRunOnServiceThread();
- return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
+ return mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
}
@ServiceThreadOnly
@@ -1092,11 +997,7 @@
* Whether a device of the specified physical address is connected to ARC enabled port.
*/
boolean isConnectedToArcPort(int physicalAddress) {
- int portId = pathToPortId(physicalAddress);
- if (portId != Constants.INVALID_PORT_ID) {
- return mPortInfoMap.get(portId).isArcSupported();
- }
- return false;
+ return mHdmiCecNetwork.isConnectedToArcPort(physicalAddress);
}
@ServiceThreadOnly
@@ -1168,7 +1069,7 @@
}
return true;
}
-
+ getHdmiCecNetwork().handleCecMessage(message);
if (dispatchMessageToLocalDevice(message)) {
return true;
}
@@ -1183,7 +1084,7 @@
@ServiceThreadOnly
private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
assertRunOnServiceThread();
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
if (device.dispatchMessage(message)
&& message.getDestination() != Constants.ADDR_BROADCAST) {
return true;
@@ -1209,12 +1110,12 @@
if (connected && !isTvDevice()
&& getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
if (isSwitchDevice()) {
- initPortInfo();
+ mHdmiCecNetwork.initPortInfo();
HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
}
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
localDevice.init();
@@ -1224,9 +1125,14 @@
allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
}
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onHotplug(portId, connected);
}
+
+ if (!connected) {
+ mHdmiCecNetwork.removeDevicesConnectedToPort(portId);
+ }
+
announceHotplugEvent(portId, connected);
}
@@ -1262,7 +1168,7 @@
List<HdmiCecLocalDevice> getAllLocalDevices() {
assertRunOnServiceThread();
- return mCecController.getLocalDeviceList();
+ return mHdmiCecNetwork.getLocalDeviceList();
}
/**
@@ -1275,8 +1181,14 @@
*
* @param logicalAddress logical address of the remote device that might have the same logical
* address as the current device.
+ * @param physicalAddress physical address of the given device.
*/
- protected void checkLogicalAddressConflictAndReallocate(int logicalAddress) {
+ protected void checkLogicalAddressConflictAndReallocate(int logicalAddress,
+ int physicalAddress) {
+ // The given device is a local device. No logical address conflict.
+ if (physicalAddress == getPhysicalAddress()) {
+ return;
+ }
for (HdmiCecLocalDevice device : getAllLocalDevices()) {
if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) {
HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo());
@@ -1616,8 +1528,7 @@
return null;
}
if (audioSystem() != null) {
- HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
- for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
+ for (HdmiDeviceInfo info : mHdmiCecNetwork.getSafeCecDevicesLocked()) {
if (info.getPhysicalAddress() == activeSource.physicalAddress) {
return info;
}
@@ -1649,7 +1560,7 @@
}
int activePath = tv.getActivePath();
if (activePath != HdmiDeviceInfo.PATH_INVALID) {
- HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
+ HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath);
return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
}
return null;
@@ -1752,7 +1663,7 @@
return;
}
if (mCecController != null) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType);
if (localDevice == null) {
Slog.w(TAG, "Local device not available to send key event.");
return;
@@ -1774,7 +1685,7 @@
Slog.w(TAG, "CEC controller not available to send volume key event.");
return;
}
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType);
if (localDevice == null) {
Slog.w(TAG, "Local device " + deviceType
+ " not available to send volume key event.");
@@ -1888,7 +1799,7 @@
public int getPhysicalAddress() {
enforceAccessPermission();
synchronized (mLock) {
- return mPhysicalAddress;
+ return mHdmiCecNetwork.getPhysicalAddress();
}
}
@@ -1934,13 +1845,8 @@
enforceAccessPermission();
// No need to hold the lock for obtaining TV device as the local device instance
// is preserved while the HDMI control is enabled.
- HdmiCecLocalDeviceTv tv = tv();
- synchronized (mLock) {
- List<HdmiDeviceInfo> cecDevices = (tv == null)
- ? Collections.<HdmiDeviceInfo>emptyList()
- : tv.getSafeExternalInputsLocked();
- return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
- }
+ return HdmiUtils.mergeToUnmodifiableList(mHdmiCecNetwork.getSafeExternalInputsLocked(),
+ getMhlDevicesLocked());
}
// Returns all the CEC devices on the bus including system audio, switch,
@@ -1948,19 +1854,7 @@
@Override
public List<HdmiDeviceInfo> getDeviceList() {
enforceAccessPermission();
- HdmiCecLocalDeviceTv tv = tv();
- if (tv != null) {
- synchronized (mLock) {
- return tv.getSafeCecDevicesLocked();
- }
- } else {
- HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
- synchronized (mLock) {
- return (audioSystem == null)
- ? Collections.<HdmiDeviceInfo>emptyList()
- : audioSystem.getSafeCecDevicesLocked();
- }
- }
+ return mHdmiCecNetwork.getSafeCecDevicesLocked();
}
@Override
@@ -2089,7 +1983,7 @@
runOnServiceThread(new Runnable() {
@Override
public void run() {
- HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType);
if (device == null) {
Slog.w(TAG, "Local device not available");
return;
@@ -2117,7 +2011,7 @@
mhlDevice.sendStandby();
return;
}
- HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType);
if (device == null) {
device = audioSystem();
}
@@ -2262,7 +2156,7 @@
runOnServiceThread(new Runnable() {
@Override
public void run() {
- HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType);
if (device == null) {
Slog.w(TAG, "Local device not available");
return;
@@ -2339,8 +2233,7 @@
pw.increaseIndent();
mMhlController.dump(pw);
pw.decreaseIndent();
-
- HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+ mHdmiCecNetwork.dump(pw);
if (mCecController != null) {
pw.println("mCecController: ");
pw.increaseIndent();
@@ -2832,7 +2725,7 @@
}
public HdmiCecLocalDeviceTv tv() {
- return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
+ return (HdmiCecLocalDeviceTv) mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
}
boolean isTvDevice() {
@@ -2857,11 +2750,11 @@
protected HdmiCecLocalDevicePlayback playback() {
return (HdmiCecLocalDevicePlayback)
- mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
}
public HdmiCecLocalDeviceAudioSystem audioSystem() {
- return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
+ return (HdmiCecLocalDeviceAudioSystem) mHdmiCecNetwork.getLocalDevice(
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
@@ -2991,7 +2884,7 @@
}
private boolean canGoToStandby() {
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
if (!device.canGoToStandby()) return false;
}
return true;
@@ -3025,7 +2918,7 @@
private void disableDevices(PendingActionClearedCallback callback) {
if (mCecController != null) {
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.disableDevice(mStandbyMessageReceived, callback);
}
}
@@ -3039,7 +2932,8 @@
return;
}
mCecController.clearLogicalAddress();
- mCecController.clearLocalDevices();
+ mHdmiCecNetwork.clearLogicalAddress();
+ mHdmiCecNetwork.clearLocalDevices();
}
@ServiceThreadOnly
@@ -3051,7 +2945,7 @@
return;
}
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onStandby(mStandbyMessageReceived, standbyAction);
}
mStandbyMessageReceived = false;
@@ -3411,7 +3305,7 @@
// input change listener should be the one describing the corresponding HDMI port.
HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
HdmiDeviceInfo info = (device != null) ? device.getInfo()
- : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
+ : mHdmiCecNetwork.getDeviceForPortId(portId);
invokeInputChangeListener(info);
}
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 7670dcc..ece78bfa2 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -148,7 +148,8 @@
}
private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
- BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly);
+ BitSet currentInfos = infoListToBitSet(
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false), audioOnly);
BitSet polledResult = addressListToBitSet(ackedAddress);
// At first, check removed devices.
@@ -225,11 +226,11 @@
mayCancelOneTouchRecord(removedAddress);
mayDisableSystemAudioAndARC(removedAddress);
- tv().removeCecDevice(removedAddress);
+ localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress);
}
private void mayChangeRoutingPath(int address) {
- HdmiDeviceInfo info = tv().getCecDeviceInfo(address);
+ HdmiDeviceInfo info = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(address);
if (info != null) {
tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 6753368..edc7bd9 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -19,6 +19,7 @@
import android.util.Slog;
import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
+
import java.io.UnsupportedEncodingException;
/**
@@ -164,7 +165,8 @@
private void addDeviceInfo() {
// The device should be in the device list with default information.
- if (!tv().isInDeviceList(mDeviceLogicalAddress, mDevicePhysicalAddress)) {
+ if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress,
+ mDevicePhysicalAddress)) {
Slog.w(TAG, String.format("Device not found (%02x, %04x)",
mDeviceLogicalAddress, mDevicePhysicalAddress));
return;
@@ -176,7 +178,7 @@
mDeviceLogicalAddress, mDevicePhysicalAddress,
tv().getPortId(mDevicePhysicalAddress),
mDeviceType, mVendorId, mDisplayName);
- tv().addCecDevice(deviceInfo);
+ localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
// Consume CEC messages we already got for this newly found device.
tv().processDelayedMessages(mDeviceLogicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index e78a86c..53f9a10 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -19,6 +19,7 @@
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
+import android.provider.Settings.Global;
import android.util.Slog;
import java.util.ArrayList;
@@ -54,6 +55,8 @@
private int mPowerStatusCounter = 0;
+ private HdmiCecLocalDeviceSource mSource;
+
// Factory method. Ensures arguments are valid.
static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
int targetAddress, IHdmiControlCallback callback) {
@@ -74,27 +77,33 @@
@Override
boolean start() {
+ // Because only source device can create this action, it's safe to cast.
+ mSource = source();
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
broadcastActiveSource();
+ // If the device is not an audio system itself, request the connected audio system to
+ // turn on.
+ if (shouldTurnOnConnectedAudioSystem()) {
+ sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(),
+ Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true));
+ }
queryDevicePowerStatus();
addTimer(mState, HdmiConfig.TIMEOUT_MS);
return true;
}
private void broadcastActiveSource() {
- // Because only source device can create this action, it's safe to cast.
- HdmiCecLocalDeviceSource source = source();
- source.mService.setAndBroadcastActiveSourceFromOneDeviceType(
+ mSource.mService.setAndBroadcastActiveSourceFromOneDeviceType(
mTargetAddress, getSourcePath(), "OneTouchPlayAction#broadcastActiveSource()");
// When OneTouchPlay is called, client side should be responsible to send out the intent
// of which internal source, for example YouTube, it would like to switch to.
// Here we only update the active port and the active source records in the local
// device as well as claiming Active Source.
- if (source.mService.audioSystem() != null) {
- source = source.mService.audioSystem();
+ if (mSource.mService.audioSystem() != null) {
+ mSource = mSource.mService.audioSystem();
}
- source.setRoutingPort(Constants.CEC_SWITCH_HOME);
- source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
+ mSource.setRoutingPort(Constants.CEC_SWITCH_HOME);
+ mSource.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
private void queryDevicePowerStatus() {
@@ -151,4 +160,14 @@
Slog.e(TAG, "Callback failed:" + e);
}
}
+
+ private boolean shouldTurnOnConnectedAudioSystem() {
+ HdmiControlService service = mSource.mService;
+ if (service.isAudioSystemDevice()) {
+ return false;
+ }
+ String sendStandbyOnSleep = service.readStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, "");
+ return sendStandbyOnSleep.equals(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index a62d0b6..909fcda 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -20,7 +20,9 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.util.SparseIntArray;
+
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
import java.util.List;
/**
@@ -111,7 +113,8 @@
}
private void queryPowerStatus() {
- List<HdmiDeviceInfo> deviceInfos = tv().getDeviceInfoList(false);
+ List<HdmiDeviceInfo> deviceInfos =
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
resetPowerStatus(deviceInfos);
for (HdmiDeviceInfo info : deviceInfos) {
final int logicalAddress = info.getLogicalAddress();
@@ -137,7 +140,8 @@
}
private void updatePowerStatus(int logicalAddress, int newStatus, boolean remove) {
- tv().updateDevicePowerStatus(logicalAddress, newStatus);
+ localDevice().mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
+ newStatus);
if (remove) {
mPowerStatus.delete(logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
index 6c8694e..6c147ed 100644
--- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
@@ -17,8 +17,8 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -160,7 +160,9 @@
}
switch (timeoutState) {
case STATE_WAIT_FOR_ROUTING_INFORMATION:
- HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath);
+ HdmiDeviceInfo device =
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath(
+ mCurrentRoutingPath);
if (device != null && mQueryDevicePowerStatus) {
int deviceLogicalAddress = device.getLogicalAddress();
queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index cdb73d8..9ca4d35 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -556,8 +556,8 @@
@Override
public void registerLocationListener(String provider, LocationRequest request,
- ILocationListener listener, String packageName, String attributionTag,
- String listenerId) {
+ ILocationListener listener, String packageName, @Nullable String attributionTag,
+ @Nullable String listenerId) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
listenerId);
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
@@ -582,7 +582,7 @@
@Override
public void registerLocationPendingIntent(String provider, LocationRequest request,
- PendingIntent pendingIntent, String packageName, String attributionTag) {
+ PendingIntent pendingIntent, String packageName, @Nullable String attributionTag) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
AppOpsManager.toReceiverId(pendingIntent));
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 179fb7d..b4a1723 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -77,7 +77,6 @@
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.Preconditions;
@@ -115,7 +114,6 @@
class LocationProviderManager extends
ListenerMultiplexer<Object, LocationProviderManager.LocationTransport,
- LocationProviderManager.LocationListenerOperation,
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
@@ -225,16 +223,8 @@
}
}
- protected interface LocationListenerOperation extends ListenerOperation<LocationTransport> {
- /**
- * Must be implemented to return the location this operation intends to deliver.
- */
- @Nullable
- Location getLocation();
- }
-
protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
- LocationTransport, LocationListenerOperation> {
+ LocationTransport> {
private final @PermissionLevel int mPermissionLevel;
@@ -310,7 +300,7 @@
protected void onProviderListenerUnregister() {}
@Override
- protected final LocationListenerOperation onActive() {
+ protected final void onActive() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -319,11 +309,12 @@
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
onHighPowerUsageChanged();
- return onProviderListenerActive();
+
+ onProviderListenerActive();
}
@Override
- protected final LocationListenerOperation onInactive() {
+ protected final void onInactive() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -332,24 +323,21 @@
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
}
- return onProviderListenerInactive();
+
+ onProviderListenerInactive();
}
/**
* Subclasses may override this instead of {@link #onActive()}.
*/
@GuardedBy("mLock")
- protected LocationListenerOperation onProviderListenerActive() {
- return null;
- }
+ protected void onProviderListenerActive() {}
/**
* Subclasses may override this instead of {@link #onInactive()} ()}.
*/
@GuardedBy("mLock")
- protected LocationListenerOperation onProviderListenerInactive() {
- return null;
- }
+ protected void onProviderListenerInactive() {}
@Override
public final LocationRequest getRequest() {
@@ -357,10 +345,8 @@
}
@GuardedBy("mLock")
- final void initializeLastLocation(@Nullable Location location) {
- if (mLastLocation == null) {
- mLastLocation = location;
- }
+ final void setLastDeliveredLocation(@Nullable Location location) {
+ mLastLocation = location;
}
@GuardedBy("mLock")
@@ -541,16 +527,8 @@
}
@GuardedBy("mLock")
- @Override
- protected final LocationListenerOperation onExecuteOperation(
- LocationListenerOperation operation) {
- mLastLocation = operation.getLocation();
- return super.onExecuteOperation(operation);
- }
-
- @GuardedBy("mLock")
- @Nullable
- abstract LocationListenerOperation acceptLocationChange(Location fineLocation);
+ abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ Location fineLocation);
@Override
public String toString() {
@@ -656,7 +634,7 @@
@GuardedBy("mLock")
@Override
- protected final LocationListenerOperation onProviderListenerActive() {
+ protected final void onProviderListenerActive() {
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
@@ -679,12 +657,10 @@
getRequest().isLocationSettingsIgnored(),
maxLocationAgeMs);
if (lastLocation != null) {
- return acceptLocationChange(lastLocation);
+ executeOperation(acceptLocationChange(lastLocation));
}
}
}
-
- return null;
}
@Override
@@ -703,9 +679,9 @@
}
@GuardedBy("mLock")
- @Nullable
@Override
- LocationListenerOperation acceptLocationChange(Location fineLocation) {
+ @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ Location fineLocation) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -748,16 +724,20 @@
return null;
}
- return new LocationListenerOperation() {
- @Override
- public Location getLocation() {
- return location;
- }
+ // deliver location
+ return new ListenerOperation<LocationTransport>() {
+
+ private boolean mUseWakeLock;
@Override
public void onPreExecute() {
+ mUseWakeLock = !location.isFromMockProvider();
+
+ // update last delivered location
+ setLastDeliveredLocation(location);
+
// don't acquire a wakelock for mock locations to prevent abuse
- if (!location.isFromMockProvider()) {
+ if (mUseWakeLock) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
}
}
@@ -774,13 +754,13 @@
}
listener.deliverOnLocationChanged(deliveryLocation,
- location.isFromMockProvider() ? null : mWakeLock::release);
+ mUseWakeLock ? mWakeLock::release : null);
mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
}
@Override
public void onPostExecute(boolean success) {
- if (!success && !location.isFromMockProvider()) {
+ if (!success && mUseWakeLock) {
mWakeLock.release();
}
@@ -852,7 +832,8 @@
}
@Override
- public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+ public void onOperationFailure(ListenerOperation<LocationTransport> operation,
+ Exception exception) {
onTransportFailure(exception);
}
@@ -913,7 +894,8 @@
}
@Override
- public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+ public void onOperationFailure(ListenerOperation<LocationTransport> operation,
+ Exception exception) {
onTransportFailure(exception);
}
@@ -988,28 +970,24 @@
@GuardedBy("mLock")
@Override
- protected LocationListenerOperation onProviderListenerActive() {
+ protected void onProviderListenerActive() {
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
getRequest().isLocationSettingsIgnored(),
MAX_CURRENT_LOCATION_AGE_MS);
if (lastLocation != null) {
- return acceptLocationChange(lastLocation);
+ executeOperation(acceptLocationChange(lastLocation));
}
-
- return null;
}
@GuardedBy("mLock")
@Override
- protected LocationListenerOperation onProviderListenerInactive() {
+ protected void onProviderListenerInactive() {
if (!getRequest().isLocationSettingsIgnored()) {
// if we go inactive for any reason, fail immediately
- return acceptLocationChange(null);
+ executeOperation(acceptLocationChange(null));
}
-
- return null;
}
void deliverNull() {
@@ -1035,9 +1013,9 @@
}
@GuardedBy("mLock")
- @Nullable
@Override
- LocationListenerOperation acceptLocationChange(@Nullable Location fineLocation) {
+ @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ @Nullable Location fineLocation) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -1059,36 +1037,21 @@
Location location = getPermittedLocation(fineLocation, getPermissionLevel());
- return new LocationListenerOperation() {
- @Override
- public Location getLocation() {
- return location;
+ // deliver location
+ return listener -> {
+ // if delivering to the same process, make a copy of the location first (since
+ // location is mutable)
+ Location deliveryLocation = location;
+ if (getIdentity().getPid() == Process.myPid() && location != null) {
+ deliveryLocation = new Location(location);
}
- @Override
- public void operate(LocationTransport listener) {
- // if delivering to the same process, make a copy of the location first (since
- // location is mutable)
- Location deliveryLocation = location;
- if (getIdentity().getPid() == Process.myPid() && location != null) {
- deliveryLocation = new Location(location);
- }
+ // we currently don't hold a wakelock for getCurrentLocation deliveries
+ listener.deliverOnLocationChanged(deliveryLocation, null);
+ mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
- // we currently don't hold a wakelock for getCurrentLocation deliveries
- try {
- listener.deliverOnLocationChanged(deliveryLocation, null);
- mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
- } catch (Exception exception) {
- if (exception instanceof RemoteException) {
- Log.w(TAG, "registration " + this + " failed", exception);
- } else {
- throw new AssertionError(exception);
- }
- }
-
- synchronized (mLock) {
- remove();
- }
+ synchronized (mLock) {
+ remove();
}
};
}
@@ -1114,7 +1077,7 @@
protected final Object mLock = new Object();
protected final String mName;
- @Nullable private final PassiveLocationProviderManager mPassiveManager;
+ private final @Nullable PassiveLocationProviderManager mPassiveManager;
protected final Context mContext;
@@ -1178,7 +1141,7 @@
protected final MockableLocationProvider mProvider;
@GuardedBy("mLock")
- @Nullable private OnAlarmListener mDelayedRegister;
+ private @Nullable OnAlarmListener mDelayedRegister;
LocationProviderManager(Context context, Injector injector, String name,
@Nullable PassiveLocationProviderManager passiveManager) {
@@ -1254,13 +1217,11 @@
return mName;
}
- @Nullable
- public CallerIdentity getIdentity() {
+ public @Nullable CallerIdentity getIdentity() {
return mProvider.getState().identity;
}
- @Nullable
- public ProviderProperties getProperties() {
+ public @Nullable ProviderProperties getProperties() {
return mProvider.getState().properties;
}
@@ -1381,9 +1342,8 @@
}
}
- @Nullable
- public Location getLastLocation(CallerIdentity identity, @PermissionLevel int permissionLevel,
- boolean ignoreLocationSettings) {
+ public @Nullable Location getLastLocation(CallerIdentity identity,
+ @PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
identity.getPackageName())) {
return null;
@@ -1426,9 +1386,9 @@
* location, even if the permissionLevel is coarse. You are responsible for coarsening the
* location if necessary.
*/
- @Nullable
- public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
- boolean ignoreLocationSettings, long maximumAgeMs) {
+ public @Nullable Location getLastLocationUnsafe(int userId,
+ @PermissionLevel int permissionLevel, boolean ignoreLocationSettings,
+ long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
// find the most recent location across all users
Location lastLocation = null;
@@ -1500,8 +1460,7 @@
}
}
- @Nullable
- public ICancellationSignal getCurrentLocation(LocationRequest request,
+ public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
@@ -1519,7 +1478,7 @@
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- addRegistration(callback.asBinder(), registration);
+ putRegistration(callback.asBinder(), registration);
if (!registration.isActive()) {
// if the registration never activated, fail it immediately
registration.deliverNull();
@@ -1560,7 +1519,7 @@
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- addRegistration(listener.asBinder(), registration);
+ putRegistration(listener.asBinder(), registration);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1578,7 +1537,7 @@
synchronized (mLock) {
final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(pendingIntent, registration);
+ putRegistration(pendingIntent, registration);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1673,7 +1632,7 @@
Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
- newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation());
+ newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
super.onRegistrationReplaced(key, oldRegistration, newRegistration);
}
@@ -2056,7 +2015,9 @@
setLastLocation(location, UserHandle.USER_ALL);
// attempt listener delivery
- deliverToListeners(registration -> registration.acceptLocationChange(location));
+ deliverToListeners(registration -> {
+ return registration.acceptLocationChange(location);
+ });
// notify passive provider
if (mPassiveManager != null) {
@@ -2194,8 +2155,7 @@
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- @Nullable
- private Location getPermittedLocation(@Nullable Location fineLocation,
+ private @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
@PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2250,10 +2210,10 @@
private static class LastLocation {
- @Nullable private Location mFineLocation;
- @Nullable private Location mCoarseLocation;
- @Nullable private Location mFineBypassLocation;
- @Nullable private Location mCoarseBypassLocation;
+ private @Nullable Location mFineLocation;
+ private @Nullable Location mCoarseLocation;
+ private @Nullable Location mFineBypassLocation;
+ private @Nullable Location mCoarseBypassLocation;
public void clearMock() {
if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
@@ -2275,8 +2235,8 @@
mCoarseLocation = null;
}
- @Nullable
- public Location get(@PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
+ public @Nullable Location get(@PermissionLevel int permissionLevel,
+ boolean ignoreLocationSettings) {
switch (permissionLevel) {
case PERMISSION_FINE:
if (ignoreLocationSettings) {
@@ -2337,13 +2297,12 @@
private static class SingleUseCallback extends IRemoteCallback.Stub implements Runnable,
CancellationSignal.OnCancelListener {
- @Nullable
- public static SingleUseCallback wrap(@Nullable Runnable callback) {
+ public static @Nullable SingleUseCallback wrap(@Nullable Runnable callback) {
return callback == null ? null : new SingleUseCallback(callback);
}
@GuardedBy("this")
- @Nullable private Runnable mCallback;
+ private @Nullable Runnable mCallback;
private SingleUseCallback(Runnable callback) {
mCallback = Objects.requireNonNull(callback);
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index c91ee82..7a59cba 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -41,7 +41,6 @@
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.PendingIntentUtils;
import com.android.server.location.LocationPermissions;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -60,8 +59,8 @@
* Manages all geofences.
*/
public class GeofenceManager extends
- ListenerMultiplexer<GeofenceKey, PendingIntent, ListenerOperation<PendingIntent>,
- GeofenceManager.GeofenceRegistration, LocationRequest> implements
+ ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
+ LocationRequest> implements
LocationListener {
private static final String TAG = "GeofenceManager";
@@ -121,12 +120,10 @@
}
@Override
- protected ListenerOperation<PendingIntent> onActive() {
+ protected void onActive() {
Location location = getLastLocation();
if (location != null) {
- return onLocationChanged(location);
- } else {
- return null;
+ executeOperation(onLocationChanged(location));
}
}
@@ -304,7 +301,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(new GeofenceKey(pendingIntent, geofence),
+ putRegistration(new GeofenceKey(pendingIntent, geofence),
new GeofenceRegistration(geofence, callerIdentity, pendingIntent));
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index ec48d4c..7592d22 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -32,7 +32,6 @@
import android.os.Process;
import android.util.ArraySet;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.location.listeners.BinderListenerRegistration;
@@ -60,7 +59,7 @@
*/
public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInterface,
TMergedRegistration> extends
- ListenerMultiplexer<IBinder, TListener, ListenerOperation<TListener>,
+ ListenerMultiplexer<IBinder, TListener,
GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration>
.GnssListenerRegistration, TMergedRegistration> {
@@ -231,7 +230,7 @@
TListener listener) {
final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(listener.asBinder(),
+ putRegistration(listener.asBinder(),
createRegistration(request, callerIdentity, listener));
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 74284f3..4a3f94f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -63,18 +63,16 @@
@Nullable
@Override
- protected ListenerOperation<IGnssMeasurementsListener> onActive() {
+ protected void onActive() {
mLocationAttributionHelper.reportHighPowerLocationStart(
getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey());
- return null;
}
@Nullable
@Override
- protected ListenerOperation<IGnssMeasurementsListener> onInactive() {
+ protected void onInactive() {
mLocationAttributionHelper.reportHighPowerLocationStop(
getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey());
- return null;
}
}
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index bc675ce..d6b179b 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -23,8 +23,6 @@
import android.os.RemoteException;
import android.util.Log;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
/**
* A registration that works with IBinder keys, and registers a DeathListener to automatically
* remove the registration if the binder dies. The key for this registration must either be an
@@ -34,7 +32,7 @@
* @param <TListener> listener type
*/
public abstract class BinderListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
+ RemoteListenerRegistration<TRequest, TListener> implements
Binder.DeathRecipient {
/**
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 0318ffb..6b93616 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -75,13 +75,11 @@
*
* @param <TKey> key type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
* @param <TRegistration> registration type
* @param <TMergedRegistration> merged registration type
*/
public abstract class ListenerMultiplexer<TKey, TListener,
- TListenerOperation extends ListenerOperation<TListener>,
- TRegistration extends ListenerRegistration<TListener, TListenerOperation>,
+ TRegistration extends ListenerRegistration<TListener>,
TMergedRegistration> {
@GuardedBy("mRegistrations")
@@ -218,10 +216,26 @@
protected void onInactive() {}
/**
- * Adds a new registration with the given key. This method cannot be called to add a
- * registration re-entrantly.
+ * Puts a new registration with the given key, replacing any previous registration under the
+ * same key. This method cannot be called to put a registration re-entrantly.
*/
- protected final void addRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
+ protected final void putRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
+ replaceRegistration(key, key, registration);
+ }
+
+ /**
+ * Atomically removes the registration with the old key and adds a new registration with the
+ * given key. If there was a registration for the old key,
+ * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
+ * invoked for the new registration and key instead of
+ * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
+ * the same key. The old key may be the same value as the new key, in which case this function
+ * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
+ * be called to add a registration re-entrantly.
+ */
+ protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
+ @NonNull TRegistration registration) {
+ Objects.requireNonNull(oldKey);
Objects.requireNonNull(key);
Objects.requireNonNull(registration);
@@ -229,6 +243,9 @@
// adding listeners reentrantly is not supported
Preconditions.checkState(!mReentrancyGuard.isReentrant());
+ // new key may only have a prior registration if the oldKey is the same as the key
+ Preconditions.checkArgument(oldKey == key || !mRegistrations.containsKey(key));
+
// since adding a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. further, we buffer service updates since adding a registration may
@@ -241,9 +258,11 @@
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
- int index = mRegistrations.indexOfKey(key);
+ int index = mRegistrations.indexOfKey(oldKey);
if (index >= 0) {
- oldRegistration = removeRegistration(index, false);
+ oldRegistration = removeRegistration(index, oldKey != key);
+ }
+ if (oldKey == key && index >= 0) {
mRegistrations.setValueAt(index, registration);
} else {
mRegistrations.put(key, registration);
@@ -316,7 +335,7 @@
* re-entrancy, and may be called to remove a registration re-entrantly.
*/
protected final void removeRegistration(@NonNull Object key,
- @NonNull ListenerRegistration<?, ?> registration) {
+ @NonNull ListenerRegistration<?> registration) {
synchronized (mRegistrations) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
@@ -478,15 +497,9 @@
if (++mActiveRegistrationsCount == 1) {
onActive();
}
- TListenerOperation operation = registration.onActive();
- if (operation != null) {
- execute(registration, operation);
- }
+ registration.onActive();
} else {
- TListenerOperation operation = registration.onInactive();
- if (operation != null) {
- execute(registration, operation);
- }
+ registration.onInactive();
if (--mActiveRegistrationsCount == 0) {
onInactive();
}
@@ -502,16 +515,16 @@
* change the active state of the registration.
*/
protected final void deliverToListeners(
- @NonNull Function<TRegistration, TListenerOperation> function) {
+ @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
- TListenerOperation operation = function.apply(registration);
+ ListenerOperation<TListener> operation = function.apply(registration);
if (operation != null) {
- execute(registration, operation);
+ registration.executeOperation(operation);
}
}
}
@@ -526,14 +539,14 @@
* deliverToListeners(registration -> operation);
* </pre>
*/
- protected final void deliverToListeners(@NonNull TListenerOperation operation) {
+ protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
- execute(registration, operation);
+ registration.executeOperation(operation);
}
}
}
@@ -545,10 +558,6 @@
onRegistrationActiveChanged(registration);
}
- private void execute(TRegistration registration, TListenerOperation operation) {
- registration.executeInternal(operation);
- }
-
/**
* Dumps debug information.
*/
@@ -606,7 +615,7 @@
@GuardedBy("mRegistrations")
private int mGuardCount;
@GuardedBy("mRegistrations")
- private @Nullable ArraySet<Entry<Object, ListenerRegistration<?, ?>>> mScheduledRemovals;
+ private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
@@ -622,7 +631,7 @@
}
@GuardedBy("mRegistrations")
- void markForRemoval(Object key, ListenerRegistration<?, ?> registration) {
+ void markForRemoval(Object key, ListenerRegistration<?> registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
@@ -641,7 +650,7 @@
@Override
public void close() {
- ArraySet<Entry<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null;
+ ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
Preconditions.checkState(mGuardCount > 0);
if (--mGuardCount == 0) {
@@ -656,7 +665,7 @@
try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
final int size = scheduledRemovals.size();
for (int i = 0; i < size; i++) {
- Entry<Object, ListenerRegistration<?, ?>> entry = scheduledRemovals.valueAt(i);
+ Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
removeRegistration(entry.getKey(), entry.getValue());
}
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index d7ecbcb..fa21b3a 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -17,11 +17,9 @@
package com.android.server.location.listeners;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.listeners.ListenerExecutor;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -31,11 +29,8 @@
* request, and an executor responsible for listener invocations.
*
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public class ListenerRegistration<TListener,
- TListenerOperation extends ListenerOperation<TListener>> implements
- ListenerExecutor {
+public class ListenerRegistration<TListener> implements ListenerExecutor {
private final Executor mExecutor;
@@ -70,18 +65,14 @@
* returns a non-null operation, that operation will be invoked for the listener. Invoked
* while holding the owning multiplexer's internal lock.
*/
- protected @Nullable TListenerOperation onActive() {
- return null;
- }
+ protected void onActive() {}
/**
* May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
* a non-null operation, that operation will be invoked for the listener. Invoked while holding
* the owning multiplexer's internal lock.
*/
- protected @Nullable TListenerOperation onInactive() {
- return null;
- }
+ protected void onInactive() {}
public final boolean isActive() {
return mActive;
@@ -114,27 +105,20 @@
protected void onListenerUnregister() {}
/**
- * May be overridden by subclasses, however should rarely be needed. Invoked whenever a listener
- * operation is submitted for execution, and allows the registration a chance to replace the
- * listener operation or perform related bookkeeping. There is no guarantee a listener operation
- * submitted or returned here will ever be invoked. Will always be invoked on the calling
- * thread.
- */
- protected TListenerOperation onExecuteOperation(@NonNull TListenerOperation operation) {
- return operation;
- }
-
- /**
* May be overridden by subclasses to handle listener operation failures. The default behavior
* is to further propagate any exceptions. Will always be invoked on the executor thread.
*/
- protected void onOperationFailure(TListenerOperation operation, Exception exception) {
- throw new AssertionError(exception);
+ protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+ throw new AssertionError(e);
}
- final void executeInternal(@NonNull TListenerOperation operation) {
- executeSafely(mExecutor, () -> mListener,
- onExecuteOperation(Objects.requireNonNull(operation)), this::onOperationFailure);
+ /**
+ * Executes the given listener operation, invoking
+ * {@link #onOperationFailure(ListenerOperation, Exception)} in case the listener operation
+ * fails.
+ */
+ protected final void executeOperation(@Nullable ListenerOperation<TListener> operation) {
+ executeSafely(mExecutor, () -> mListener, operation, this::onOperationFailure);
}
@Override
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index e57b532..0aafb29 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -21,8 +21,6 @@
import android.location.util.identity.CallerIdentity;
import android.util.Log;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
/**
* A registration that works with PendingIntent keys, and registers a CancelListener to
* automatically remove the registration if the PendingIntent is canceled. The key for this
@@ -32,8 +30,7 @@
* @param <TListener> listener type
*/
public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
- PendingIntent.CancelListener {
+ RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
/**
* Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
@@ -73,7 +70,7 @@
protected void onPendingIntentListenerUnregister() {}
@Override
- public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+ protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof PendingIntent.CanceledException) {
Log.w(getOwner().getTag(), "registration " + this + " removed", e);
remove();
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
index 242bf32..4eca577 100644
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
@@ -24,7 +24,6 @@
import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.FgThread;
import java.util.Objects;
@@ -38,11 +37,9 @@
*
* @param <TRequest> request type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public abstract class RemoteListenerRegistration<TRequest, TListener,
- TListenerOperation extends ListenerOperation<TListener>> extends
- RemovableListenerRegistration<TRequest, TListener, TListenerOperation> {
+public abstract class RemoteListenerRegistration<TRequest, TListener> extends
+ RemovableListenerRegistration<TRequest, TListener> {
@VisibleForTesting
public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index d3b5f66..618ff24 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -18,8 +18,6 @@
import android.annotation.Nullable;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -29,11 +27,9 @@
*
* @param <TRequest> request type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public abstract class RemovableListenerRegistration<TRequest, TListener,
- TListenerOperation extends ListenerOperation<TListener>> extends
- RequestListenerRegistration<TRequest, TListener, TListenerOperation> {
+public abstract class RemovableListenerRegistration<TRequest, TListener> extends
+ RequestListenerRegistration<TRequest, TListener> {
private volatile @Nullable Object mKey;
@@ -47,8 +43,7 @@
* with. Often this is easiest to accomplish by defining registration subclasses as non-static
* inner classes of the multiplexer they are to be used with.
*/
- protected abstract ListenerMultiplexer<?, ? super TListener, ?
- super TListenerOperation, ?, ?> getOwner();
+ protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
index d97abae..0c2fc91 100644
--- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
@@ -16,8 +16,6 @@
package com.android.server.location.listeners;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
import java.util.concurrent.Executor;
/**
@@ -25,11 +23,9 @@
*
* @param <TRequest> request type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public class RequestListenerRegistration<TRequest, TListener,
- TListenerOperation extends ListenerOperation<TListener>> extends
- ListenerRegistration<TListener, TListenerOperation> {
+public class RequestListenerRegistration<TRequest, TListener> extends
+ ListenerRegistration<TListener> {
private final TRequest mRequest;
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index 26ace47..72803ac 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -119,6 +119,33 @@
}
}
+ /**
+ * Change the startable state if the app has crashed or ANR'd during loading.
+ * If the app is not loading (i.e., fully loaded), this event doesn't change startable state.
+ */
+ public void onCrashOrAnr() {
+ if (DEBUG) {
+ Slog.i(TAG, "received package crash or ANR event");
+ }
+ final boolean startableStateChanged;
+ synchronized (mLock) {
+ if (mStartableState.isStartable() && mLoadingState.isLoading()) {
+ // Changing from startable -> unstartable only if app is still loading.
+ mStartableState.adoptNewStartableStateLocked(false);
+ startableStateChanged = true;
+ } else {
+ // If the app is fully loaded, the crash or ANR is caused by the app itself, so
+ // we do not change the startable state.
+ startableStateChanged = false;
+ }
+ }
+ if (startableStateChanged) {
+ mHandler.post(PooledLambda.obtainRunnable(
+ IncrementalStates::reportStartableState,
+ IncrementalStates.this).recycleOnUse());
+ }
+ }
+
private void reportStartableState() {
final Callback callback;
final boolean startable;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8eebf2a..b679c0f 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
@@ -32,6 +33,7 @@
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1018,6 +1020,32 @@
}
@Override
+ public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts,
+ UserHandle user) {
+ if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
+ throw new ActivityNotFoundException("Activity could not be found");
+ }
+
+ final Intent launchIntent = getMainActivityLaunchIntent(component, user);
+ if (launchIntent == null) {
+ throw new SecurityException("Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER " + component);
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ // If we reach here, we've verified that the caller has access to the profile and
+ // is launching an exported activity with CATEGORY_LAUNCHER so we can clear the
+ // calling identity to mirror the startActivityAsUser() call which does not validate
+ // the calling user
+ return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
+ FLAG_IMMUTABLE, opts, user);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
@@ -1025,9 +1053,24 @@
return;
}
+ Intent launchIntent = getMainActivityLaunchIntent(component, user);
+ if (launchIntent == null) {
+ throw new SecurityException("Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER " + component);
+ }
+ launchIntent.setSourceBounds(sourceBounds);
+
+ mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
+ callingFeatureId, launchIntent, /* resultTo= */ null,
+ Intent.FLAG_ACTIVITY_NEW_TASK, opts, user.getIdentifier());
+ }
+
+ /**
+ * Returns the main activity launch intent for the given component package.
+ */
+ private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user) {
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- launchIntent.setSourceBounds(sourceBounds);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
launchIntent.setPackage(component.getPackageName());
@@ -1066,15 +1109,12 @@
}
}
if (!canLaunch) {
- throw new SecurityException("Attempt to launch activity without "
- + " category Intent.CATEGORY_LAUNCHER " + component);
+ return null;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
- mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
- callingFeatureId, launchIntent, /* resultTo= */ null,
- Intent.FLAG_ACTIVITY_NEW_TASK, opts, user.getIdentifier());
+ return launchIntent;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 410fbe5..7eb4fd9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -222,6 +222,7 @@
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
@@ -336,6 +337,7 @@
import com.android.internal.os.Zygote;
import com.android.internal.telephony.CarrierAppUtils;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -479,7 +481,7 @@
* </pre>
*/
public class PackageManagerService extends IPackageManager.Stub
- implements PackageSender {
+ implements PackageSender, TestUtilityService {
static final String TAG = "PackageManager";
public static final boolean DEBUG_SETTINGS = false;
static final boolean DEBUG_PREFERRED = false;
@@ -819,6 +821,7 @@
boolean mPromoteSystemApps;
private final PackageManagerInternal mPmInternal;
+ private final TestUtilityService mTestUtilityService;
@GuardedBy("mLock")
@@ -1192,6 +1195,7 @@
public IPermissionManager permissionManagerService;
public PendingPackageBroadcasts pendingPackageBroadcasts;
public PackageManagerInternal pmInternal;
+ public TestUtilityService testUtilityService;
public ProcessLoggingHandler processLoggingHandler;
public ProtectedPackages protectedPackages;
public @NonNull String requiredInstallerPackage;
@@ -2956,6 +2960,7 @@
mPendingBroadcasts = testParams.pendingPackageBroadcasts;
mPermissionManagerService = testParams.permissionManagerService;
mPmInternal = testParams.pmInternal;
+ mTestUtilityService = testParams.testUtilityService;
mProcessLoggingHandler = testParams.processLoggingHandler;
mProtectedPackages = testParams.protectedPackages;
mSeparateProcesses = testParams.separateProcesses;
@@ -3026,6 +3031,8 @@
// Expose private service for system components to use.
mPmInternal = new PackageManagerInternalImpl();
+ LocalServices.addService(TestUtilityService.class, this);
+ mTestUtilityService = LocalServices.getService(TestUtilityService.class);
LocalServices.addService(PackageManagerInternal.class, mPmInternal);
mUserManager = injector.getUserManagerService();
mComponentResolver = injector.getComponentResolver();
@@ -12687,12 +12694,17 @@
mPermissionManager.addAllPermissionGroups(pkg, chatty);
}
+ // If a permission has had its defining app changed, or it has had its protection
+ // upgraded, we need to revoke apps that hold it
+ final List<String> permissionsWithChangedDefinition;
// Don't allow ephemeral applications to define new permissions.
if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+ permissionsWithChangedDefinition = null;
Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
+ " ignored: instant apps cannot define new permissions.");
} else {
- mPermissionManager.addAllPermissions(pkg, chatty);
+ permissionsWithChangedDefinition =
+ mPermissionManager.addAllPermissions(pkg, chatty);
}
int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
@@ -12721,7 +12733,10 @@
}
}
- if (oldPkg != null) {
+ boolean hasOldPkg = oldPkg != null;
+ boolean hasPermissionDefinitionChanges =
+ !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
+ if (hasOldPkg || hasPermissionDefinitionChanges) {
// We need to call revokeRuntimePermissionsIfGroupChanged async as permission
// revoke callbacks from this method might need to kill apps which need the
// mPackages lock on a different thread. This would dead lock.
@@ -12732,9 +12747,16 @@
// won't be granted yet, hence new packages are no problem.
final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet());
- AsyncTask.execute(() ->
+ AsyncTask.execute(() -> {
+ if (hasOldPkg) {
mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg,
- allPackageNames));
+ allPackageNames);
+ }
+ if (hasPermissionDefinitionChanges) {
+ mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ permissionsWithChangedDefinition, allPackageNames);
+ }
+ });
}
}
@@ -25843,6 +25865,20 @@
}
return ps.getIncrementalStates();
}
+
+ @Override
+ public void notifyPackageCrashOrAnr(@NonNull String packageName) {
+ final PackageSetting ps;
+ synchronized (mLock) {
+ ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(TAG, "Failed notifyPackageCrash. Package " + packageName
+ + " is not installed");
+ return;
+ }
+ }
+ ps.setStatesOnCrashOrAnr();
+ }
}
@@ -26336,9 +26372,39 @@
}
@Override
- public void holdLock(int durationMs) {
+ public IBinder getHoldLockToken() {
+ if (!Build.IS_DEBUGGABLE) {
+ throw new SecurityException("getHoldLockToken requires a debuggable build");
+ }
+
mContext.enforceCallingPermission(
- Manifest.permission.INJECT_EVENTS, "holdLock requires shell identity");
+ Manifest.permission.INJECT_EVENTS,
+ "getHoldLockToken requires INJECT_EVENTS permission");
+
+ final Binder token = new Binder();
+ token.attachInterface(this, "holdLock:" + Binder.getCallingUid());
+ return token;
+ }
+
+ @Override
+ public void verifyHoldLockToken(IBinder token) {
+ if (!Build.IS_DEBUGGABLE) {
+ throw new SecurityException("holdLock requires a debuggable build");
+ }
+
+ if (token == null) {
+ throw new SecurityException("null holdLockToken");
+ }
+
+ if (token.queryLocalInterface("holdLock:" + Binder.getCallingUid()) != this) {
+ throw new SecurityException("Invalid holdLock() token");
+ }
+ }
+
+ @Override
+ public void holdLock(IBinder token, int durationMs) {
+ mTestUtilityService.verifyHoldLockToken(token);
+
synchronized (mLock) {
SystemClock.sleep(durationMs);
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index be7c7c6..ac76cf7 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -772,6 +772,14 @@
}
/**
+ * Called to indicate that the running app has crashed or ANR'd. This might change the startable
+ * state of the package, depending on whether the package is fully loaded.
+ */
+ public void setStatesOnCrashOrAnr() {
+ incrementalStates.onCrashOrAnr();
+ }
+
+ /**
* Called to set the callback to listen for startable state changes.
*/
public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 6ffc598..155d716 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -74,6 +74,8 @@
private static final String ATTR_PACKAGE = "package";
private static final String TAG_ITEM = "item";
+ private boolean mPermissionDefinitionChanged;
+
@NonNull
private PermissionInfo mPermissionInfo;
@@ -122,6 +124,10 @@
return mPermissionInfo.packageName;
}
+ public boolean isPermissionDefinitionChanged() {
+ return mPermissionDefinitionChanged;
+ }
+
public int getType() {
return mType;
}
@@ -148,6 +154,10 @@
mReconciled = permissionInfo != null;
}
+ public void setPermissionDefinitionChanged(boolean shouldOverride) {
+ mPermissionDefinitionChanged = shouldOverride;
+ }
+
public boolean hasGids() {
return mGids.length != 0;
}
@@ -364,6 +374,7 @@
@NonNull AndroidPackage pkg, Collection<BasePermission> permissionTrees,
boolean chatty) {
// Allow system apps to redefine non-system permissions
+ boolean ownerChanged = false;
if (bp != null && !Objects.equals(bp.mPermissionInfo.packageName, p.packageName)) {
final boolean currentOwnerIsSystem;
if (!bp.mReconciled) {
@@ -389,6 +400,7 @@
String msg = "New decl " + pkg + " of permission "
+ p.name + " is system; overriding " + bp.mPermissionInfo.packageName;
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ ownerChanged = true;
bp = null;
}
}
@@ -396,6 +408,7 @@
if (bp == null) {
bp = new BasePermission(p.name, p.packageName, TYPE_MANIFEST);
}
+ boolean wasNormal = bp.isNormal();
StringBuilder r = null;
if (!bp.mReconciled) {
if (bp.mPermissionInfo.packageName == null
@@ -435,6 +448,11 @@
r.append("DUP:");
r.append(p.name);
}
+ if (bp.isRuntime() && (ownerChanged || wasNormal)) {
+ // If this is a runtime permission and the owner has changed, or this was a normal
+ // permission, then permission state should be cleaned up
+ bp.mPermissionDefinitionChanged = true;
+ }
if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
Log.d(TAG, " Permissions: " + r);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 4d43969..da4ef63 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2327,8 +2327,74 @@
}
}
- private void addAllPermissions(AndroidPackage pkg, boolean chatty) {
+ /**
+ * If permissions are upgraded to runtime, or their owner changes to the system, then any
+ * granted permissions must be revoked.
+ *
+ * @param permissionsToRevoke A list of permission names to revoke
+ * @param allPackageNames All package names
+ * @param permissionCallback Callback for permission changed
+ */
+ private void revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ @NonNull List<String> permissionsToRevoke,
+ @NonNull ArrayList<String> allPackageNames,
+ @NonNull PermissionCallback permissionCallback) {
+
+ final int[] userIds = mUserManagerInt.getUserIds();
+ final int numPermissions = permissionsToRevoke.size();
+ final int numUserIds = userIds.length;
+ final int numPackages = allPackageNames.size();
+ final int callingUid = Binder.getCallingUid();
+
+ for (int permNum = 0; permNum < numPermissions; permNum++) {
+ String permName = permissionsToRevoke.get(permNum);
+ BasePermission bp = mSettings.getPermission(permName);
+ if (bp == null || !bp.isRuntime()) {
+ continue;
+ }
+ for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
+ final int userId = userIds[userIdNum];
+ for (int packageNum = 0; packageNum < numPackages; packageNum++) {
+ final String packageName = allPackageNames.get(packageNum);
+ final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+ if (uid < Process.FIRST_APPLICATION_UID) {
+ // do not revoke from system apps
+ continue;
+ }
+ final int permissionState = checkPermissionImpl(permName, packageName,
+ userId);
+ final int flags = getPermissionFlags(permName, packageName, userId);
+ final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_POLICY_FIXED
+ | FLAG_PERMISSION_GRANTED_BY_DEFAULT
+ | FLAG_PERMISSION_GRANTED_BY_ROLE;
+ if (permissionState == PackageManager.PERMISSION_GRANTED
+ && (flags & flagMask) == 0) {
+ EventLog.writeEvent(0x534e4554, "154505240", uid,
+ "Revoking permission " + permName + " from package "
+ + packageName + " due to definition change");
+ EventLog.writeEvent(0x534e4554, "168319670", uid,
+ "Revoking permission " + permName + " from package "
+ + packageName + " due to definition change");
+ Slog.e(TAG, "Revoking permission " + permName + " from package "
+ + packageName + " due to definition change");
+ try {
+ revokeRuntimePermissionInternal(permName, packageName,
+ false, callingUid, userId, null, permissionCallback);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not revoke " + permName + " from "
+ + packageName, e);
+ }
+ }
+ }
+ }
+ bp.setPermissionDefinitionChanged(false);
+ }
+ }
+
+ private List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
final int N = ArrayUtils.size(pkg.getPermissions());
+ ArrayList<String> definitionChangedPermissions = new ArrayList<>();
for (int i=0; i<N; i++) {
ParsedPermission p = pkg.getPermissions().get(i);
@@ -2369,8 +2435,12 @@
if (bp.isInstalled()) {
p.setFlags(p.getFlags() | PermissionInfo.FLAG_INSTALLED);
}
+ if (bp.isPermissionDefinitionChanged()) {
+ definitionChangedPermissions.add(p.getName());
+ }
}
}
+ return definitionChangedPermissions;
}
private void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
@@ -4750,9 +4820,18 @@
PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage,
oldPackage, allPackageNames, mDefaultPermissionCallback);
}
+
@Override
- public void addAllPermissions(AndroidPackage pkg, boolean chatty) {
- PermissionManagerService.this.addAllPermissions(pkg, chatty);
+ public void revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ @NonNull List<String> permissionsToRevoke,
+ @NonNull ArrayList<String> allPackageNames) {
+ PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ permissionsToRevoke, allPackageNames, mDefaultPermissionCallback);
+ }
+
+ @Override
+ public List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
+ return PermissionManagerService.this.addAllPermissions(pkg, chatty);
}
@Override
public void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 5ea3458..20e9c5d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -256,12 +256,26 @@
@NonNull ArrayList<String> allPackageNames);
/**
+ * Some permissions might have been owned by a non-system package, and the system then defined
+ * said permission. Some other permissions may one have been install permissions, but are now
+ * runtime or higher. These permissions should be revoked.
+ *
+ * @param permissionsToRevoke A list of permission names to revoke
+ * @param allPackageNames All packages
+ */
+ public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ @NonNull List<String> permissionsToRevoke,
+ @NonNull ArrayList<String> allPackageNames);
+
+ /**
* Add all permissions in the given package.
* <p>
* NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
* the permission settings.
+ *
+ * @return A list of BasePermissions that were updated, and need to be revoked from packages
*/
- public abstract void addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
+ public abstract List<String> addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty);
public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 9d08b1b..f50ce1ab8e8 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -131,7 +131,6 @@
*/
private static final int IGNORE_CALLER = -1;
private static final int INVALID_DELAY = -1;
- private static final int INVALID_TRANSITION_TYPE = -1;
// Preallocated strings we are sending to tron, so we don't have to allocate a new one every
// time we log.
@@ -232,22 +231,19 @@
static TransitionInfo create(@NonNull ActivityRecord r,
@NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
boolean processRunning, boolean processSwitch, int startResult) {
- int transitionType = INVALID_TRANSITION_TYPE;
+ if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
+ return null;
+ }
+ final int transitionType;
if (processRunning) {
- if (startResult == START_SUCCESS) {
- transitionType = TYPE_TRANSITION_WARM_LAUNCH;
- } else if (startResult == START_TASK_TO_FRONT) {
- transitionType = TYPE_TRANSITION_HOT_LAUNCH;
- }
- } else if (startResult == START_SUCCESS || startResult == START_TASK_TO_FRONT) {
+ transitionType = r.attachedToProcess()
+ ? TYPE_TRANSITION_HOT_LAUNCH
+ : TYPE_TRANSITION_WARM_LAUNCH;
+ } else {
// Task may still exist when cold launching an activity and the start result will be
// set to START_TASK_TO_FRONT. Treat this as a COLD launch.
transitionType = TYPE_TRANSITION_COLD_LAUNCH;
}
- if (transitionType == INVALID_TRANSITION_TYPE) {
- // That means the startResult is neither START_SUCCESS nor START_TASK_TO_FRONT.
- return null;
- }
return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
processSwitch);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d6a1f4a..0a7f08b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -499,7 +499,6 @@
// process that it is hidden.
private boolean mLastDeferHidingClient; // If true we will defer setting mClientVisible to false
// and reporting to the client that it is hidden.
- private boolean mSetToSleep; // have we told the activity to sleep?
boolean nowVisible; // is this activity's window visible?
boolean mClientVisibilityDeferred;// was the visibility change message to client deferred?
boolean idle; // has the activity gone idle?
@@ -906,7 +905,6 @@
pw.print(" finishing="); pw.println(finishing);
pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused);
pw.print(" inHistory="); pw.print(inHistory);
- pw.print(" setToSleep="); pw.print(mSetToSleep);
pw.print(" idle="); pw.print(idle);
pw.print(" mStartingWindowState=");
pw.println(startingWindowStateToString(mStartingWindowState));
@@ -1364,6 +1362,7 @@
} else if (mLetterbox != null) {
mLetterbox.hide();
}
+ task.maybeUpdateLetterboxBounds(this, getLetterboxParams(w));
}
void updateLetterboxSurface(WindowState winHint) {
@@ -1377,6 +1376,12 @@
}
}
+ @Nullable
+ private Rect getLetterboxParams(WindowState w) {
+ boolean isLetterboxed = w.isLetterboxedAppWindow() && fillsParent();
+ return isLetterboxed ? getBounds() : null;
+ }
+
Rect getLetterboxInsets() {
if (mLetterbox != null) {
return mLetterbox.getInsets();
@@ -4675,14 +4680,13 @@
return false;
}
- // Check if the activity is on a sleeping display
- // TODO b/163993448 mSetToSleep is required when restarting an existing activity, try to
- // remove it if possible.
- if (mSetToSleep && mDisplayContent.isSleeping()) {
- return false;
+ // Check if the activity is on a sleeping display, canTurnScreenOn will also check
+ // keyguard visibility
+ if (mDisplayContent.isSleeping()) {
+ return canTurnScreenOn();
+ } else {
+ return mStackSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
-
- return mStackSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) {
@@ -4719,7 +4723,6 @@
stack.mUndrawnActivitiesBelowTopTranslucent.add(this);
}
setVisibility(true);
- mSetToSleep = false;
app.postPendingUiCleanMsg(true);
if (reportToClient) {
mClientVisibilityDeferred = false;
@@ -5118,9 +5121,6 @@
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
StopActivityItem.obtain(configChangeFlags));
- if (stack.shouldSleepOrShutDownActivities()) {
- setSleeping(true);
- }
mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
} catch (Exception e) {
// Maybe just ignore exceptions here... if the process has crashed, our death
@@ -5711,10 +5711,6 @@
return mVisibleRequested || nowVisible || mState == PAUSING || mState == RESUMED;
}
- void setSleeping(boolean sleeping) {
- mSetToSleep = sleeping;
- }
-
static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null || r.getParent() == null) {
@@ -6552,14 +6548,20 @@
mCompatDisplayInsets = new CompatDisplayInsets(mDisplayContent, this);
}
- @VisibleForTesting
- void clearSizeCompatMode() {
+ void clearSizeCompatMode(boolean recomputeTask) {
mSizeCompatScale = 1f;
mSizeCompatBounds = null;
mCompatDisplayInsets = null;
- // Recompute from Task because letterbox can also happen on Task level.
- task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration());
+ if (recomputeTask) {
+ // Recompute from Task because letterbox can also happen on Task level.
+ task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration());
+ }
+ }
+
+ @VisibleForTesting
+ void clearSizeCompatMode() {
+ clearSizeCompatMode(true /* recomputeTask */);
}
@Override
@@ -7555,7 +7557,7 @@
return false;
}
final Task stack = getRootTask();
- return stack != null
+ return mCurrentLaunchCanTurnScreenOn && stack != null
&& mStackSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a8079cf..a068d2b 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -820,7 +820,6 @@
}
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
- r.setSleeping(false);
r.forceNewConfig = false;
mService.getAppWarningsLocked().onStartActivity(r);
r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c8a8f81..25b2523 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1195,7 +1195,12 @@
}
}
- mService.onStartActivitySetDidAppSwitch();
+ // Only allow app switching to be resumed if activity is not a restricted background
+ // activity, otherwise any background activity started in background task can stop
+ // home button protection mode.
+ if (!restrictedBgActivity) {
+ mService.onStartActivitySetDidAppSwitch();
+ }
mController.doPendingActivityLaunches(false);
mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
@@ -1260,6 +1265,20 @@
return false;
}
+ // Always allow home application to start activities.
+ if (mService.mHomeProcess != null && callingUid == mService.mHomeProcess.mUid) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start allowed for home app callingUid (" + callingUid + ")");
+ }
+ return false;
+ }
+
+ // App switching will be allowed if BAL app switching flag is not enabled, or if
+ // its app switching rule allows it.
+ // This is used to block background activity launch even if the app is still
+ // visible to user after user clicking home button.
+ final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
+
// don't abort if the callingUid has a visible window or is a persistent system process
final int callingUidProcState = mService.getUidState(callingUid);
final boolean callingUidHasAnyVisibleWindow =
@@ -1269,7 +1288,8 @@
|| callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
final boolean isCallingUidPersistentSystemProcess =
callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
- if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
+ if ((appSwitchAllowed && callingUidHasAnyVisibleWindow)
+ || isCallingUidPersistentSystemProcess) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start allowed: callingUidHasAnyVisibleWindow = " + callingUid
+ ", isCallingUidPersistentSystemProcess = "
@@ -1295,6 +1315,7 @@
|| realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
if (realCallingUid != callingUid) {
// don't abort if the realCallingUid has a visible window
+ // TODO(b/171459802): We should check appSwitchAllowed also
if (realCallingUidHasAnyVisibleWindow) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid
@@ -1376,7 +1397,7 @@
// don't abort if the callerApp or other processes of that uid are allowed in any way
if (callerApp != null) {
// first check the original calling process
- if (callerApp.areBackgroundActivityStartsAllowed()) {
+ if (callerApp.areBackgroundActivityStartsAllowed(appSwitchAllowed)) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Background activity start allowed: callerApp process (pid = "
+ callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed");
@@ -1389,7 +1410,8 @@
if (uidProcesses != null) {
for (int i = uidProcesses.size() - 1; i >= 0; i--) {
final WindowProcessController proc = uidProcesses.valueAt(i);
- if (proc != callerApp && proc.areBackgroundActivityStartsAllowed()) {
+ if (proc != callerApp
+ && proc.areBackgroundActivityStartsAllowed(appSwitchAllowed)) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG,
"Background activity start allowed: process " + proc.getPid()
@@ -2546,6 +2568,10 @@
private void resumeTargetStackIfNeeded() {
if (mDoResume) {
+ final ActivityRecord next = mTargetStack.topRunningActivity(true /* focusableOnly */);
+ if (next != null) {
+ next.setCurrentLaunchCanTurnScreenOn(true);
+ }
mRootWindowContainer.resumeFocusedStacksTopActivities(mTargetStack, null, mOptions);
} else {
ActivityOptions.abort(mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8e5add9..6749cdf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -39,6 +39,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ApplicationInfo.FLAG_FACTORY_TEST;
import static android.content.pm.ConfigurationInfo.GL_ES_VERSION_UNDEFINED;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
@@ -159,9 +160,11 @@
import android.app.admin.DevicePolicyCache;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
+import android.app.compat.CompatChanges;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.EnterPipRequestedItem;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -340,6 +343,12 @@
/** This activity is being relaunched due to a free-resize operation. */
public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
+ /**
+ * Apps are blocked from starting activities in the foreground after the user presses home.
+ */
+ @ChangeId
+ public static final long BLOCK_ACTIVITY_STARTS_AFTER_HOME = 159433730L;
+
Context mContext;
/**
@@ -1295,6 +1304,7 @@
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
@@ -2624,8 +2634,35 @@
throw new SecurityException(msg);
}
+ /**
+ * Return true if app switch protection will be handled by background activity launch logic.
+ */
+ boolean getBalAppSwitchesProtectionEnabled() {
+ return CompatChanges.isChangeEnabled(BLOCK_ACTIVITY_STARTS_AFTER_HOME);
+ }
+
+ /**
+ * Return true if app switching is allowed.
+ */
+ boolean getBalAppSwitchesAllowed() {
+ if (getBalAppSwitchesProtectionEnabled()) {
+ // Apps no longer able to start BAL again until app switching is resumed.
+ return mAppSwitchesAllowedTime == 0;
+ } else {
+ // Legacy behavior, BAL logic won't block app switching.
+ return true;
+ }
+ }
+
boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
int callingPid, int callingUid, String name) {
+
+ // Background activity launch logic replaces app switching protection, so allow
+ // apps to start activity here now.
+ if (getBalAppSwitchesProtectionEnabled()) {
+ return true;
+ }
+
if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
return true;
}
@@ -3606,7 +3643,7 @@
}
/** This can be called with or without the global lock held. */
- private void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+ void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
if (!getRecentTasks().isCallerRecents(Binder.getCallingUid())) {
mAmInternal.enforceCallingPermission(permission, func);
}
@@ -4645,7 +4682,10 @@
mAppSwitchesAllowedTime = SystemClock.uptimeMillis() + APP_SWITCH_DELAY_TIME;
mLastStopAppSwitchesTime = SystemClock.uptimeMillis();
mDidAppSwitch = false;
- getActivityStartController().schedulePendingActivityLaunches(APP_SWITCH_DELAY_TIME);
+ // If BAL app switching enabled, app switches are blocked not delayed.
+ if (!getBalAppSwitchesProtectionEnabled()) {
+ getActivityStartController().schedulePendingActivityLaunches(APP_SWITCH_DELAY_TIME);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7c5c435..7641de5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1303,7 +1303,7 @@
@Override
boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
- ConfigurationContainer requestingContainer) {
+ WindowContainer requestingContainer) {
final Configuration config = updateOrientation(
getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */);
// If display rotation class tells us that it doesn't consider app requested orientation,
@@ -5018,6 +5018,13 @@
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
+ static boolean canReuseExistingTask(int windowingMode, int activityType) {
+ // Existing Tasks can be reused if a new stack will be created anyway, or for the Dream -
+ // because there can only ever be one DreamActivity.
+ return alwaysCreateStack(windowingMode, activityType)
+ || activityType == ACTIVITY_TYPE_DREAM;
+ }
+
@Nullable
Task getFocusedStack() {
return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedStack);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 72ecf6b..2ea4b57 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -118,6 +116,10 @@
* without having a WM lock.
*/
volatile boolean mAnimationCompleted = false;
+ /**
+ * The display on which the drag is happening. If it goes into a different display this will
+ * be updated.
+ */
DisplayContent mDisplayContent;
@Nullable private ValueAnimator mAnimator;
@@ -308,7 +310,9 @@
// Pause rotations before a drag.
ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during drag");
- mDisplayContent.getDisplayRotation().pause();
+ mService.mRoot.forAllDisplays(dc -> {
+ dc.getDisplayRotation().pause();
+ });
}
void tearDown() {
@@ -323,7 +327,9 @@
// Resume rotations after a drag.
ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after drag");
- mDisplayContent.getDisplayRotation().resume();
+ mService.mRoot.forAllDisplays(dc -> {
+ dc.getDisplayRotation().resume();
+ });
}
}
@@ -371,9 +377,9 @@
Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
}
- mDisplayContent.forAllWindows(w -> {
+ mService.mRoot.forAllWindows(w -> {
sendDragStartedLocked(w, touchX, touchY, mDataDescription, mData);
- }, false /* traverseTopToBottom */ );
+ }, false /* traverseTopToBottom */);
}
/* helper - send a ACTION_DRAG_STARTED event, if the
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c1e518b..04b3030 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -55,6 +55,7 @@
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
@@ -2779,7 +2780,8 @@
if (allowDelay) {
result &= stack.goToSleepIfPossible(shuttingDown);
} else {
- stack.goToSleep();
+ stack.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ !PRESERVE_WINDOWS);
}
}
return result;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 72edb86..6fbd351 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -20,7 +20,12 @@
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_SHORTCUT_ID;
+import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -49,6 +54,7 @@
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -297,18 +303,25 @@
*/
@VisibleForTesting
public void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ throw new IllegalStateException("Need to validate before calling identify is cleared");
+ }
final ClipDescription desc = data != null ? data.getDescription() : null;
if (desc == null) {
return;
}
// Ensure that only one of the app mime types are set
final boolean hasActivity = desc.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY);
- int appMimeTypeCount = (hasActivity ? 1 : 0);
+ final boolean hasShortcut = desc.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+ final boolean hasTask = desc.hasMimeType(MIMETYPE_APPLICATION_TASK);
+ int appMimeTypeCount = (hasActivity ? 1 : 0)
+ + (hasShortcut ? 1 : 0)
+ + (hasTask ? 1 : 0);
if (appMimeTypeCount == 0) {
return;
} else if (appMimeTypeCount > 1) {
throw new IllegalArgumentException("Can not specify more than one of activity, "
- + "or task mime types");
+ + "shortcut, or task mime types");
}
// Ensure that data is provided and that they are intents
if (data.getItemCount() == 0) {
@@ -344,6 +357,28 @@
} finally {
Binder.restoreCallingIdentity(origId);
}
+ } else if (hasShortcut) {
+ mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+ "performDrag");
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final Intent intent = data.getItemAt(i).getIntent();
+ final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (!intent.hasExtra(EXTRA_SHORTCUT_ID)
+ || TextUtils.isEmpty(intent.getStringExtra(EXTRA_SHORTCUT_ID))
+ || user == null) {
+ throw new IllegalArgumentException("Clip item must include the shortcut id and "
+ + "the user to launch for.");
+ }
+ }
+ } else if (hasTask) {
+ mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+ "performDrag");
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final Intent intent = data.getItemAt(i).getIntent();
+ if (intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID) == INVALID_TASK_ID) {
+ throw new IllegalArgumentException("Clip item must include the task id.");
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6804684..ef1a3be 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -528,6 +528,11 @@
// {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root activity.
boolean mSupportsPictureInPicture;
+ // Activity bounds if this task or its top activity is presented in letterbox mode and
+ // {@code null} otherwise.
+ @Nullable
+ private Rect mLetterboxActivityBounds;
+
// Whether the task is currently being drag-resized
private boolean mDragResizing;
private int mDragResizeMode;
@@ -790,6 +795,10 @@
*/
boolean mTaskAppearedSent;
+ // If the sending of the task appear signal should be deferred until this flag is set back to
+ // false.
+ private boolean mDeferTaskAppear;
+
/**
* This task was created by the task organizer which has the following implementations.
* <ul>
@@ -802,14 +811,20 @@
@VisibleForTesting
boolean mCreatedByOrganizer;
+ // Tracking cookie for the creation of this task.
+ IBinder mLaunchCookie;
+
/**
* Don't use constructor directly. Use {@link TaskDisplayArea#createStackUnchecked()} instead.
*/
- Task(ActivityTaskManagerService atmService, int id, int activityType,
- ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+ Task(ActivityTaskManagerService atmService, int id, int activityType, ActivityInfo info,
+ Intent intent, boolean createdByOrganizer, boolean deferTaskAppear,
+ IBinder launchCookie) {
this(atmService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
null /*taskDescription*/, null /*stack*/);
mCreatedByOrganizer = createdByOrganizer;
+ mLaunchCookie = launchCookie;
+ mDeferTaskAppear = deferTaskAppear;
setActivityType(activityType);
}
@@ -2821,16 +2836,25 @@
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+ final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
// Resolve override windowing mode to fullscreen for home task (even on freeform
// display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
- final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
+ // Do not allow non-resizable non-pinned tasks to be in a multi-window mode - they should
+ // use their parent's windowing mode, or fullscreen.
+ if (!isResizeable() && windowingMode != WINDOWING_MODE_PINNED
+ && WindowConfiguration.inMultiWindowMode(windowingMode)) {
+ windowingMode = WindowConfiguration.inMultiWindowMode(parentWindowingMode)
+ ? WINDOWING_MODE_FULLSCREEN : parentWindowingMode;
+ getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+ }
+
if (isLeafTask()) {
resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */);
}
@@ -2921,13 +2945,27 @@
final int parentWidth = parentBounds.width();
final int parentHeight = parentBounds.height();
- final float aspect = ((float) parentHeight) / parentWidth;
+ float aspect = Math.max(parentWidth, parentHeight)
+ / (float) Math.min(parentWidth, parentHeight);
+
+ // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the
+ // extra available space.
+ if (refActivity != null) {
+ final float maxAspectRatio = refActivity.info.maxAspectRatio;
+ final float minAspectRatio = refActivity.info.minAspectRatio;
+ if (aspect > maxAspectRatio && maxAspectRatio != 0) {
+ aspect = maxAspectRatio;
+ } else if (aspect < minAspectRatio) {
+ aspect = minAspectRatio;
+ }
+ }
+
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- final int height = (int) (parentWidth / aspect);
+ final int height = (int) Math.rint(parentWidth / aspect);
final int top = parentBounds.centerY() - height / 2;
outBounds.set(parentBounds.left, top, parentBounds.right, top + height);
} else {
- final int width = (int) (parentHeight * aspect);
+ final int width = (int) Math.rint(parentHeight / aspect);
final int left = parentBounds.centerX() - width / 2;
outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
}
@@ -3283,7 +3321,7 @@
@Override
public boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
- ConfigurationContainer requestingContainer) {
+ WindowContainer requestingContainer) {
if (super.onDescendantOrientationChanged(freezeDisplayToken, requestingContainer)) {
return true;
}
@@ -3291,6 +3329,18 @@
// No one in higher hierarchy handles this request, let's adjust our bounds to fulfill
// it if possible.
if (getParent() != null) {
+ final ActivityRecord activity = requestingContainer.asActivityRecord();
+ if (activity != null) {
+ // Clear the size compat cache to recompute the bounds for requested orientation;
+ // otherwise when Task#computeFullscreenBounds(), it will not try to do Task level
+ // letterboxing because app may prefer to keep its original size (size compat).
+ //
+ // Normally, ActivityRecord#clearSizeCompatMode() recomputes from its parent Task,
+ // which is the leaf Task. However, because this orientation request is new to all
+ // Tasks, pass false to clearSizeCompatMode, and trigger onConfigurationChanged from
+ // here (root Task) to make sure all Tasks are up-to-date.
+ activity.clearSizeCompatMode(false /* recomputeTask */);
+ }
onConfigurationChanged(getParent().getConfiguration());
return true;
}
@@ -3321,7 +3371,9 @@
}
boolean isResizeable(boolean checkSupportsPip) {
- return (mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
+ final boolean forceResizable = mAtmService.mForceResizableActivities
+ && getActivityType() == ACTIVITY_TYPE_STANDARD;
+ return (forceResizable || ActivityInfo.isResizeableMode(mResizeMode)
|| (checkSupportsPip && mSupportsPictureInPicture));
}
@@ -4046,11 +4098,18 @@
info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
info.topActivityType = top.getActivityType();
info.isResizeable = isResizeable();
+ // Don't query getTopNonFinishingActivity().getBounds() directly because when fillTaskInfo
+ // is triggered for the first time after activities change, getBounds() may return non final
+ // bounds, e.g. fullscreen bounds instead of letterboxed bounds. To work around this,
+ // assigning bounds from ActivityRecord#layoutLetterbox when they are ready.
+ info.letterboxActivityBounds = Rect.copyOrNull(mLetterboxActivityBounds);
+ info.positionInParent = getRelativePosition();
info.pictureInPictureParams = getPictureInPictureParams();
info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info
: null;
+ info.addLaunchCookie(mLaunchCookie);
forAllActivities(r -> {
info.addLaunchCookie(r.mLaunchCookie);
});
@@ -4064,6 +4123,21 @@
? null : rootActivity.pictureInPictureArgs;
}
+ void maybeUpdateLetterboxBounds(
+ ActivityRecord activityRecord, @Nullable Rect letterboxActivityBounds) {
+ if (isOrganized()
+ && mReuseActivitiesReport.top == activityRecord
+ // Want to force update only if letterbox bounds have changed.
+ && !Objects.equals(
+ mLetterboxActivityBounds,
+ letterboxActivityBounds)) {
+ mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds);
+ // Forcing update to reduce visual jank during the transition.
+ mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
+ this, /* force= */ true);
+ }
+ }
+
/**
* Returns a {@link TaskInfo} with information from this task.
*/
@@ -4805,6 +4879,13 @@
return mHasBeenVisible;
}
+ void setDeferTaskAppear(boolean deferTaskAppear) {
+ mDeferTaskAppear = deferTaskAppear;
+ if (!mDeferTaskAppear) {
+ sendTaskAppeared();
+ }
+ }
+
/** In the case that these conditions are true, we want to send the Task to the organizer:
* 1. An organizer has been set
* 2. The Task was created by the organizer
@@ -4819,6 +4900,10 @@
return false;
}
+ if (mDeferTaskAppear) {
+ return false;
+ }
+
if (mCreatedByOrganizer) {
return true;
}
@@ -5252,14 +5337,12 @@
taskDisplayArea.moveHomeStackToFront(reason + " returnToHome");
}
- if (isRootTask()) {
- taskDisplayArea.positionChildAt(POSITION_TOP, this, false /* includingParents */,
- reason);
- }
+ final Task lastFocusedTask = isRootTask() ? taskDisplayArea.getFocusedStack() : null;
if (task == null) {
task = this;
}
task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */);
+ taskDisplayArea.updateLastFocusedRootTask(lastFocusedTask, reason);
}
/**
@@ -5282,8 +5365,9 @@
if (parentTask != null) {
parentTask.moveToBack(reason, this);
} else {
- displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/,
- reason);
+ final Task lastFocusedTask = displayArea.getFocusedStack();
+ displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/);
+ displayArea.updateLastFocusedRootTask(lastFocusedTask, reason);
}
if (task != null && task != this) {
positionChildAtBottom(task);
@@ -5330,8 +5414,6 @@
}
void awakeFromSleepingLocked() {
- // Ensure activities are no longer sleeping.
- forAllActivities((Consumer<ActivityRecord>) (r) -> r.setSleeping(false));
if (mPausingActivity != null) {
Slog.d(TAG, "awakeFromSleepingLocked: previously pausing activity didn't pause");
mPausingActivity.activityPaused(true);
@@ -5385,27 +5467,13 @@
}
if (shouldSleep) {
- goToSleep();
+ ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ !PRESERVE_WINDOWS);
}
return shouldSleep;
}
- void goToSleep() {
- // Make sure all visible activities are now sleeping. This will update the activity's
- // visibility and onStop() will be called.
- forAllActivities((r) -> {
- if (r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING, STOPPED)) {
- r.setSleeping(true);
- }
- });
-
- // Ensure visibility after updating sleep states without updating configuration,
- // as activities are about to be sent to sleep.
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS);
- }
-
private boolean containsActivityFromStack(List<ActivityRecord> rs) {
for (ActivityRecord r : rs) {
if (r.getRootTask() == this) {
@@ -5702,8 +5770,9 @@
boolean preserveWindows, boolean notifyClients) {
mStackSupervisor.beginActivityVisibilityUpdate();
try {
- mEnsureActivitiesVisibleHelper.process(starting, configChanges, preserveWindows,
- notifyClients);
+ forAllLeafTasks(task -> task.mEnsureActivitiesVisibleHelper.process(
+ starting, configChanges, preserveWindows, notifyClients),
+ true /* traverseTopToBottom */);
if (mTranslucentActivityWaiting != null &&
mUndrawnActivitiesBelowTopTranslucent.isEmpty()) {
@@ -5890,6 +5959,8 @@
if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.getWindowingMode() != WINDOWING_MODE_FREEFORM
&& taskDisplayArea.allResumedActivitiesComplete()) {
+ // The activity may be waiting for stop, but that is no longer appropriate for it.
+ mStackSupervisor.mStoppingActivities.remove(next);
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
@@ -5936,7 +6007,6 @@
// The activity may be waiting for stop, but that is no longer
// appropriate for it.
mStackSupervisor.mStoppingActivities.remove(next);
- next.setSleeping(false);
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
@@ -6209,7 +6279,6 @@
EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
next.getTask().mTaskId, next.shortComponentName);
- next.setSleeping(false);
mAtmService.getAppWarningsLocked().onResumeActivity(next);
next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
next.clearOptionsLocked();
@@ -6810,13 +6879,10 @@
// get calculated incorrectly.
mDisplayContent.deferUpdateImeTarget();
- // Shift all activities with this task up to the top
- // of the stack, keeping them in the same internal order.
- positionChildAtTop(tr);
-
// Don't refocus if invisible to current user
final ActivityRecord top = tr.getTopNonFinishingActivity();
if (top == null || !top.okToShowLocked()) {
+ positionChildAtTop(tr);
if (top != null) {
mStackSupervisor.mRecentTasks.add(top.getTask());
}
@@ -6824,20 +6890,15 @@
return;
}
- // Set focus to the top running activity of this stack.
- final ActivityRecord r = topRunningActivity();
- if (r != null) {
- r.moveFocusableActivityToTop(reason);
- }
+ // Set focus to the top running activity of this task and move all its parents to top.
+ top.moveFocusableActivityToTop(reason);
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
if (noAnimation) {
mDisplayContent.prepareAppTransitionOld(TRANSIT_OLD_NONE,
false /* alwaysKeepCurrent */);
mDisplayContent.prepareAppTransition(TRANSIT_NONE);
- if (r != null) {
- mStackSupervisor.mNoAnimActivities.add(r);
- }
+ mStackSupervisor.mNoAnimActivities.add(top);
ActivityOptions.abort(options);
} else {
updateTransitLocked(TRANSIT_OLD_TASK_TO_FRONT, TRANSIT_TO_FRONT,
@@ -7176,7 +7237,7 @@
ActivityRecord source, ActivityOptions options) {
Task task;
- if (DisplayContent.alwaysCreateStack(getWindowingMode(), getActivityType())) {
+ if (DisplayContent.canReuseExistingTask(getWindowingMode(), getActivityType())) {
// This stack will only contain one task, so just return itself since all stacks ara now
// tasks and all tasks are now stacks.
task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity);
@@ -7441,6 +7502,12 @@
outPos.y -= outset;
}
+ private Point getRelativePosition() {
+ Point position = new Point();
+ getRelativePosition(position);
+ return position;
+ }
+
boolean shouldIgnoreInput() {
if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) {
return true;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9392666..e721319 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -50,6 +50,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
import android.os.UserHandle;
import android.util.IntArray;
import android.util.Slog;
@@ -334,29 +335,6 @@
return true;
}
- void positionChildAt(int position, Task child, boolean includingParents,
- String updateLastFocusedTaskReason) {
- final Task prevFocusedTask = updateLastFocusedTaskReason != null ? getFocusedStack() : null;
-
- positionChildAt(position, child, includingParents);
-
- if (updateLastFocusedTaskReason == null) {
- return;
- }
-
- final Task currentFocusedStack = getFocusedStack();
- if (currentFocusedStack == prevFocusedTask) {
- return;
- }
-
- mLastFocusedStack = prevFocusedTask;
- EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser,
- mDisplayContent.mDisplayId,
- currentFocusedStack == null ? -1 : currentFocusedStack.getRootTaskId(),
- mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
- updateLastFocusedTaskReason);
- }
-
@Override
void positionChildAt(int position, Task child, boolean includingParents) {
final boolean moveToTop = position >= getChildCount() - 1;
@@ -996,6 +974,13 @@
false /* createdByOrganizer */);
}
+ Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
+ Intent intent, boolean createdByOrganizer) {
+ return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */,
+ false /* createdByOrganizer */ , false /* deferTaskAppear */,
+ null /* launchCookie */);
+ }
+
/**
* Creates a stack matching the input windowing mode and activity type on this display.
*
@@ -1013,10 +998,14 @@
* @param intent The intent that started this task.
* @param createdByOrganizer @{code true} if this is created by task organizer, @{code false}
* otherwise.
+ * @param deferTaskAppear @{code true} if the task appeared signal should be deferred.
+ * @param launchCookie Launch cookie used for tracking/association of the task we are
+ * creating.
* @return The newly created stack.
*/
Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
- Intent intent, boolean createdByOrganizer) {
+ Intent intent, boolean createdByOrganizer, boolean deferTaskAppear,
+ IBinder launchCookie) {
if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) {
// Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
// anything else should be passing it in anyways...except for the task organizer.
@@ -1048,7 +1037,7 @@
final int stackId = getNextStackId();
return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent,
- createdByOrganizer);
+ createdByOrganizer, deferTaskAppear, launchCookie);
}
/** @return the root task to create the next task in. */
@@ -1078,8 +1067,9 @@
}
@VisibleForTesting
- Task createStackUnchecked(int windowingMode, int activityType, int stackId,
- boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+ Task createStackUnchecked(int windowingMode, int activityType, int stackId, boolean onTop,
+ ActivityInfo info, Intent intent, boolean createdByOrganizer, boolean deferTaskAppear,
+ IBinder launchCookie) {
if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
+ "activity type.");
@@ -1097,7 +1087,7 @@
}
final Task stack = new Task(mAtmService, stackId, activityType,
- info, intent, createdByOrganizer);
+ info, intent, createdByOrganizer, deferTaskAppear, launchCookie);
if (launchRootTask != null) {
launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
if (onTop) {
@@ -1189,6 +1179,24 @@
return mLastFocusedStack;
}
+ void updateLastFocusedRootTask(Task prevFocusedTask, String updateLastFocusedTaskReason) {
+ if (updateLastFocusedTaskReason == null) {
+ return;
+ }
+
+ final Task currentFocusedTask = getFocusedStack();
+ if (currentFocusedTask == prevFocusedTask) {
+ return;
+ }
+
+ mLastFocusedStack = prevFocusedTask;
+ EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser,
+ mDisplayContent.mDisplayId,
+ currentFocusedTask == null ? -1 : currentFocusedTask.getRootTaskId(),
+ mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
+ updateLastFocusedTaskReason);
+ }
+
boolean allResumedActivitiesComplete() {
for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 6504f00..a70efbc 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -53,6 +53,7 @@
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Consumer;
@@ -155,9 +156,8 @@
}
void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) {
- if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
- // Skip if the task has not yet received taskAppeared(), except for tasks created
- // by the organizer that don't receive that signal
+ if (!task.mTaskAppearedSent) {
+ // Skip if the task has not yet received taskAppeared().
return;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task info changed taskId=%d", task.mTaskId);
@@ -178,9 +178,8 @@
void onBackPressedOnTaskRoot(Task task) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task back pressed on root taskId=%d",
task.mTaskId);
- if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
- // Skip if the task has not yet received taskAppeared(), except for tasks created
- // by the organizer that don't receive that signal
+ if (!task.mTaskAppearedSent) {
+ // Skip if the task has not yet received taskAppeared().
return;
}
if (!task.isOrganized()) {
@@ -401,30 +400,39 @@
}
@Override
- public RunningTaskInfo createRootTask(int displayId, int windowingMode) {
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
enforceStackPermission("createRootTask()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId);
if (display == null) {
- return null;
+ ProtoLog.e(WM_DEBUG_WINDOW_ORGANIZER,
+ "createRootTask unknown displayId=%d", displayId);
+ return;
}
- ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
- displayId, windowingMode);
- final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode,
- ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
- true /* createdByOrganizer */);
- RunningTaskInfo out = task.getTaskInfo();
- mLastSentTaskInfos.put(task, out);
- return out;
+ createRootTask(display, windowingMode, launchCookie);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ @VisibleForTesting
+ Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
+ display.mDisplayId, windowingMode);
+ // We want to defer the task appear signal until the task is fully created and attached to
+ // to the hierarchy so that the complete starting configuration is in the task info we send
+ // over to the organizer.
+ final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode,
+ ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
+ true /* createdByOrganizer */, true /* deferTaskAppear */, launchCookie);
+ task.setDeferTaskAppear(false /* deferTaskAppear */);
+ return task;
+ }
+
@Override
public boolean deleteRootTask(WindowContainerToken token) {
enforceStackPermission("deleteRootTask()");
@@ -475,6 +483,12 @@
boolean changed = lastInfo == null
|| mTmpTaskInfo.topActivityType != lastInfo.topActivityType
|| mTmpTaskInfo.isResizeable != lastInfo.isResizeable
+ || !Objects.equals(
+ mTmpTaskInfo.letterboxActivityBounds,
+ lastInfo.letterboxActivityBounds)
+ || !Objects.equals(
+ mTmpTaskInfo.positionInParent,
+ lastInfo.positionInParent)
|| mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
|| mTmpTaskInfo.getConfiguration().windowConfiguration.getWindowingMode()
!= lastInfo.getConfiguration().windowConfiguration.getWindowingMode()
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1e938a4..0f6b62b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1144,7 +1144,7 @@
* @return {@code true} if handled; {@code false} otherwise.
*/
boolean onDescendantOrientationChanged(@Nullable IBinder freezeDisplayToken,
- @Nullable ConfigurationContainer requestingContainer) {
+ @Nullable WindowContainer requestingContainer) {
final WindowContainer parent = getParent();
if (parent == null) {
return false;
@@ -1156,7 +1156,7 @@
/**
* Check if this container or its parent will handle orientation changes from descendants. It's
* different from the return value of {@link #onDescendantOrientationChanged(IBinder,
- * ConfigurationContainer)} in the sense that the return value of this method tells if this
+ * WindowContainer)} in the sense that the return value of this method tells if this
* container or its parent will handle the request eventually, while the return value of the
* other method is if it handled the request synchronously.
*
@@ -1230,7 +1230,7 @@
* to ensure it gets correct configuration.
*/
void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken,
- @Nullable ConfigurationContainer requestingContainer) {
+ @Nullable WindowContainer requestingContainer) {
if (mOrientation == orientation) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 045bc4e..0eadbf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -153,6 +153,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.TestUtilityService;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.ContentObserver;
@@ -564,6 +565,7 @@
final AppOpsManager mAppOps;
final PackageManagerInternal mPmInternal;
+ private final TestUtilityService mTestUtilityService;
final DisplayWindowSettings mDisplayWindowSettings;
@@ -1265,6 +1267,7 @@
mAppOps.startWatchingMode(AppOpsManager.OP_TOAST_WINDOW, null, opListener);
mPmInternal = LocalServices.getService(PackageManagerInternal.class);
+ mTestUtilityService = LocalServices.getService(TestUtilityService.class);
final IntentFilter suspendPackagesFilter = new IntentFilter();
suspendPackagesFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
suspendPackagesFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
@@ -8349,9 +8352,8 @@
}
@Override
- public void holdLock(int durationMs) {
- mContext.enforceCallingPermission(
- Manifest.permission.INJECT_EVENTS, "holdLock requires shell identity");
+ public void holdLock(IBinder token, int durationMs) {
+ mTestUtilityService.verifyHoldLockToken(token);
synchronized (mGlobalLock) {
SystemClock.sleep(durationMs);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2e7905c..2d69dcb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -505,29 +505,33 @@
}
}
- boolean areBackgroundActivityStartsAllowed() {
- // allow if any activity in the caller has either started or finished very recently, and
- // it must be started or finished after last stop app switches time.
- final long now = SystemClock.uptimeMillis();
- if (now - mLastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS
- || now - mLastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
- // if activity is started and finished before stop app switch time, we should not
- // let app to be able to start background activity even it's in grace period.
- if (mLastActivityLaunchTime > mAtm.getLastStopAppSwitchesTime()
- || mLastActivityFinishTime > mAtm.getLastStopAppSwitchesTime()) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[WindowProcessController(" + mPid
- + ")] Activity start allowed: within "
- + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
+ boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed) {
+ // If app switching is not allowed, we ignore all the start activity grace period
+ // exception so apps cannot start itself in onPause() after pressing home button.
+ if (appSwitchAllowed) {
+ // allow if any activity in the caller has either started or finished very recently, and
+ // it must be started or finished after last stop app switches time.
+ final long now = SystemClock.uptimeMillis();
+ if (now - mLastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS
+ || now - mLastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
+ // if activity is started and finished before stop app switch time, we should not
+ // let app to be able to start background activity even it's in grace period.
+ if (mLastActivityLaunchTime > mAtm.getLastStopAppSwitchesTime()
+ || mLastActivityFinishTime > mAtm.getLastStopAppSwitchesTime()) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "[WindowProcessController(" + mPid
+ + ")] Activity start allowed: within "
+ + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
+ }
+ return true;
}
- return true;
- }
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "[WindowProcessController(" + mPid + ")] Activity start within "
- + ACTIVITY_BG_START_GRACE_PERIOD_MS
- + "ms grace period but also within stop app switch window");
- }
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "[WindowProcessController(" + mPid + ")] Activity start within "
+ + ACTIVITY_BG_START_GRACE_PERIOD_MS
+ + "ms grace period but also within stop app switch window");
+ }
+ }
}
// allow if the proc is instrumenting with background activity starts privs
if (mInstrumentingWithBackgroundActivityStartPrivileges) {
@@ -539,7 +543,7 @@
return true;
}
// allow if the caller has an activity in any foreground task
- if (hasActivityInVisibleTask()) {
+ if (appSwitchAllowed && hasActivityInVisibleTask()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "[WindowProcessController(" + mPid
+ ")] Activity start allowed: process has activity in foreground task");
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index d39c7a6..5d78f12 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "BatteryStatsService"
//#define LOG_NDEBUG 0
-#include <climits>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -28,6 +27,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <climits>
#include <unordered_map>
#include <utility>
@@ -66,10 +66,9 @@
namespace android
{
-#define LAST_RESUME_REASON "/sys/kernel/wakeup_reasons/last_resume_reason"
-#define MAX_REASON_SIZE 512
-
static bool wakeup_init = false;
+static std::mutex mReasonsMutex;
+static std::vector<std::string> mWakeupReasons;
static sem_t wakeup_sem;
extern sp<ISuspendControlService> getSuspendControl();
@@ -115,9 +114,25 @@
sp<PowerHalDeathRecipient> gDeathRecipient = new PowerHalDeathRecipient();
class WakeupCallback : public BnSuspendCallback {
- public:
- binder::Status notifyWakeup(bool success) override {
+public:
+ binder::Status notifyWakeup(bool success,
+ const std::vector<std::string>& wakeupReasons) override {
ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
+ bool reasonsCaptured = false;
+ {
+ std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
+ if (reasonsLock.try_lock() && mWakeupReasons.empty()) {
+ mWakeupReasons = std::move(wakeupReasons);
+ reasonsCaptured = true;
+ }
+ }
+ if (!reasonsCaptured) {
+ ALOGE("Failed to write wakeup reasons. Reasons dropped:");
+ for (auto wakeupReason : wakeupReasons) {
+ ALOGE("\t%s", wakeupReason.c_str());
+ }
+ }
+
int ret = sem_post(&wakeup_sem);
if (ret < 0) {
char buf[80];
@@ -157,8 +172,6 @@
// Wait for wakeup.
ALOGV("Waiting for wakeup...");
- // TODO(b/116747600): device can suspend and wakeup after sem_wait() finishes and before wakeup
- // reason is recorded, i.e. BatteryStats might occasionally miss wakeup events.
int ret = sem_wait(&wakeup_sem);
if (ret < 0) {
char buf[80];
@@ -168,20 +181,27 @@
return 0;
}
- FILE *fp = fopen(LAST_RESUME_REASON, "r");
- if (fp == NULL) {
- ALOGE("Failed to open %s", LAST_RESUME_REASON);
- return -1;
- }
-
char* mergedreason = (char*)env->GetDirectBufferAddress(outBuf);
int remainreasonlen = (int)env->GetDirectBufferCapacity(outBuf);
ALOGV("Reading wakeup reasons");
+ std::vector<std::string> wakeupReasons;
+ {
+ std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
+ if (reasonsLock.try_lock() && !mWakeupReasons.empty()) {
+ wakeupReasons = std::move(mWakeupReasons);
+ mWakeupReasons.clear();
+ }
+ }
+
+ if (wakeupReasons.empty()) {
+ return 0;
+ }
+
char* mergedreasonpos = mergedreason;
- char reasonline[128];
int i = 0;
- while (fgets(reasonline, sizeof(reasonline), fp) != NULL) {
+ for (auto wakeupReason : wakeupReasons) {
+ auto reasonline = const_cast<char*>(wakeupReason.c_str());
char* pos = reasonline;
char* endPos;
int len;
@@ -238,10 +258,6 @@
*mergedreasonpos = 0;
}
- if (fclose(fp) != 0) {
- ALOGE("Failed to close %s", LAST_RESUME_REASON);
- return -1;
- }
return mergedreasonpos - mergedreason;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ddda392..411d8d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1649,6 +1649,29 @@
}
/**
+ * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+ * If an {@code adminComponent} is specified, then the caller must be an admin and
+ * the provided component name must match the caller's UID.
+ *
+ * If a package name is provided, then the caller doesn't have to be an admin, and the
+ * provided package must belong to the caller's UID.
+ *
+ * If neither is provided, the caller identity is returned as-is.
+ *
+ * Note: this method should only be called when the caller may not be an admin. If the caller
+ * is not an admin, the ComponentName in the returned identity will be null.
+ */
+ private CallerIdentity getNonPrivilegedOrAdminCallerIdentity(
+ @Nullable ComponentName adminComponent,
+ @Nullable String callerPackage) {
+ if (adminComponent != null) {
+ return getCallerIdentity(adminComponent);
+ }
+
+ return getNonPrivilegedOrAdminCallerIdentityUsingPackage(callerPackage);
+ }
+
+ /**
* Retrieves the active admin of the caller. This method should not be called directly and
* should only be called by {@link #getAdminCallerIdentity},
* {@link #getNonPrivilegedOrAdminCallerIdentity}, {@link #getAdminCallerIdentityUsingPackage}
@@ -2315,14 +2338,6 @@
final boolean isDeviceOwner = isDeviceOwner(admin.info.getComponent(), userId);
final boolean isProfileOwner = isProfileOwner(admin.info.getComponent(), userId);
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- throw new SecurityException("Admin " + admin.info.getComponent()
- + " does not own the device");
- }
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- throw new SecurityException("Admin " + admin.info.getComponent()
- + " does not own the profile");
- }
if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) {
throw new SecurityException("Admin " + admin.info.getComponent()
+ " is not a device owner or profile owner, so may not use policy: "
@@ -2428,20 +2443,11 @@
ensureLocked();
final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
- final boolean ownsProfileOnOrganizationOwnedDevice =
- isProfileOwnerOfOrganizationOwnedDevice(admin.info.getComponent(), userId);
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- return ownsDevice;
- } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- // DO always has the PO power.
- return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile;
- } else {
- boolean allowedToUsePolicy = ownsDevice || ownsProfile
- || !DA_DISALLOWED_POLICIES.contains(reqPolicy)
- || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q;
- return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy);
- }
+ boolean allowedToUsePolicy = ownsDevice || ownsProfile
+ || !DA_DISALLOWED_POLICIES.contains(reqPolicy)
+ || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q;
+ return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy);
}
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -5488,22 +5494,17 @@
public List<String> getDelegatedScopes(ComponentName who,
String delegatePackage) throws SecurityException {
Objects.requireNonNull(delegatePackage, "Delegate package is null");
+ final CallerIdentity caller = getNonPrivilegedOrAdminCallerIdentity(who, delegatePackage);
- // Retrieve the user ID of the calling process.
- final int callingUid = mInjector.binderGetCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
+ // Ensure the caller may call this method:
+ // * Either it's an admin
+ // * Or it's an app identified by its calling package name (the
+ // getNonPrivilegedOrAdminCallerIdentity method validated the UID and package match).
+ Preconditions.checkCallAuthorization(
+ (caller.hasAdminComponent() && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || delegatePackage != null);
synchronized (getLockObject()) {
- // Ensure calling process is device/profile owner.
- if (who != null) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- // Or ensure calling process is delegatePackage itself.
- } else {
- if (!isCallingFromPackage(delegatePackage, callingUid)) {
- throw new SecurityException("Caller with uid " + callingUid + " is not "
- + delegatePackage);
- }
- }
- final DevicePolicyData policy = getUserData(userId);
+ final DevicePolicyData policy = getUserData(caller.getUserId());
// Retrieve the scopes assigned to delegatePackage, or null if no scope was given.
final List<String> scopes = policy.mDelegationMap.get(delegatePackage);
return scopes == null ? Collections.EMPTY_LIST : scopes;
@@ -7893,7 +7894,7 @@
// Current user has a managed-profile, but current user is not managed, so
// rather than moving to finalized state, go back to unmanaged once
// profile provisioning is complete.
- if (newState == DevicePolicyManager.STATE_USER_PROFILE_FINALIZED) {
+ if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
return;
}
break;
@@ -8362,37 +8363,27 @@
}
private void enforceDeviceOwnerOrManageUsers() {
- synchronized (getLockObject()) {
- if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
- mInjector.binderGetCallingUid()) != null) {
- return;
- }
+ final CallerIdentity caller = getCallerIdentity();
+ if (isDeviceOwner(caller)) {
+ return;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
}
private void enforceProfileOwnerOrSystemUser() {
- synchronized (getLockObject()) {
- if (getActiveAdminWithPolicyForUidLocked(null,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
- != null) {
- return;
- }
+ final CallerIdentity caller = getCallerIdentity();
+ if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ return;
}
- Preconditions.checkState(isCallerWithSystemUid(),
+ Preconditions.checkState(isSystemUid(caller),
"Only profile owner, device owner and system may call this method.");
}
private void enforceProfileOwnerOrFullCrossUsersPermission(CallerIdentity caller,
int userId) {
- if (userId == caller.getUserId()) {
- synchronized (getLockObject()) {
- if (getActiveAdminWithPolicyForUidLocked(null,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, caller.getUid()) != null) {
- // Device Owner/Profile Owner may access the user it runs on.
- return;
- }
- }
+ if ((userId == caller.getUserId()) && (isProfileOwner(caller) || isDeviceOwner(caller))) {
+ // Device Owner/Profile Owner may access the user it runs on.
+ return;
}
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
}
@@ -9285,10 +9276,11 @@
throw new IllegalArgumentException("profileOwner " + profileOwner + " and admin "
+ admin + " are not in the same package");
}
+ final CallerIdentity caller = getCallerIdentity(admin);
// Only allow the system user to use this method
- if (!mInjector.binderGetCallingUserHandle().isSystem()) {
- throw new SecurityException("createAndManageUser was called from non-system user");
- }
+ Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
+ "createAndManageUser was called from non-system user");
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller));
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
@@ -9298,8 +9290,6 @@
// Create user.
UserHandle user = null;
synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
final int callingUid = mInjector.binderGetCallingUid();
final long id = mInjector.binderClearCallingIdentity();
try {
@@ -11161,25 +11151,21 @@
@Override
public boolean isActiveDeviceOwner(int uid) {
- synchronized (getLockObject()) {
- return getActiveAdminWithPolicyForUidLocked(
- null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, uid) != null;
- }
+ return isDeviceOwner(new CallerIdentity(uid, null, null));
}
@Override
public boolean isActiveProfileOwner(int uid) {
- synchronized (getLockObject()) {
- return getActiveAdminWithPolicyForUidLocked(
- null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid) != null;
- }
+ return isProfileOwner(new CallerIdentity(uid, null, null));
}
@Override
public boolean isActiveSupervisionApp(int uid) {
+ if (!isProfileOwner(new CallerIdentity(uid, null, null))) {
+ return false;
+ }
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked(
- null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid);
+ final ActiveAdmin admin = getProfileOwnerAdminLocked(UserHandle.getUserId(uid));
if (admin == null) {
return false;
}
@@ -11705,6 +11691,10 @@
.getPackageName();
try {
String[] pkgs = mInjector.getIPackageManager().getPackagesForUid(appUid);
+ if (pkgs == null) {
+ return false;
+ }
+
for (String pkg : pkgs) {
if (deviceOwnerPackageName.equals(pkg)) {
return true;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 975e226..e116a35 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -76,14 +76,18 @@
import android.server.ServerProtoEnums;
import android.sysprop.VoldProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
+import android.util.TimeUtils;
import android.view.contentcapture.ContentCaptureManager;
import com.android.i18n.timezone.ZoneInfoDb;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
@@ -188,14 +192,20 @@
import com.google.android.startop.iorap.IorapForwardingService;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Timer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
-public final class SystemServer {
+/**
+ * Entry point to {@code system_server}.
+ */
+public final class SystemServer implements Dumpable {
private static final String TAG = "SystemServer";
@@ -384,6 +394,9 @@
private Future<?> mZygotePreload;
private Future<?> mBlobStoreServiceStart;
+ private final SystemServerDumper mDumper = new SystemServerDumper();
+
+
/**
* The pending WTF to be logged into dropbox.
*/
@@ -446,6 +459,75 @@
mRuntimeRestart = "1".equals(SystemProperties.get("sys.boot_completed"));
}
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ pw.printf("Runtime restart: %b\n", mRuntimeRestart);
+ pw.printf("Start count: %d\n", mStartCount);
+ pw.print("Runtime start-up time: ");
+ TimeUtils.formatDuration(mRuntimeStartUptime, pw); pw.println();
+ pw.print("Runtime start-elapsed time: ");
+ TimeUtils.formatDuration(mRuntimeStartElapsedTime, pw); pw.println();
+ }
+
+ private final class SystemServerDumper extends Binder {
+
+ @GuardedBy("mDumpables")
+ private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(4);
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final boolean hasArgs = args != null && args.length > 0;
+
+ synchronized (mDumpables) {
+ if (hasArgs && "--list".equals(args[0])) {
+ final int dumpablesSize = mDumpables.size();
+ for (int i = 0; i < dumpablesSize; i++) {
+ pw.println(mDumpables.keyAt(i));
+ }
+ return;
+ }
+
+ if (hasArgs && "--name".equals(args[0])) {
+ if (args.length < 2) {
+ pw.println("Must pass at least one argument to --name");
+ return;
+ }
+ final String name = args[1];
+ final Dumpable dumpable = mDumpables.get(name);
+ if (dumpable == null) {
+ pw.printf("No dummpable named %s\n", name);
+ return;
+ }
+
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ // Strip --name DUMPABLE from args
+ final String[] actualArgs = Arrays.copyOfRange(args, 2, args.length);
+ dumpable.dump(ipw, actualArgs);
+ }
+ return;
+ }
+
+ final int dumpablesSize = mDumpables.size();
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ for (int i = 0; i < dumpablesSize; i++) {
+ final Dumpable dumpable = mDumpables.valueAt(i);
+ ipw.printf("%s:\n", dumpable.getDumpableName());
+ ipw.increaseIndent();
+ dumpable.dump(ipw, args);
+ ipw.decreaseIndent();
+ ipw.println();
+ }
+ }
+ }
+ }
+
+ private void addDumpable(@NonNull Dumpable dumpable) {
+ synchronized (mDumpables) {
+ mDumpables.put(dumpable.getDumpableName(), dumpable);
+ }
+ }
+ }
+
private void run() {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
@@ -572,13 +654,21 @@
// Call per-process mainline module initialization.
ActivityThread.initializeMainlineModules();
+ // Sets the dumper service
+ ServiceManager.addService("system_server_dumper", mDumper);
+ mDumper.addDumpable(this);
+
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setStartInfo(mRuntimeRestart,
mRuntimeStartElapsedTime, mRuntimeStartUptime);
+ mDumper.addDumpable(mSystemServiceManager);
+
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
- SystemServerInitThreadPool.start();
+ SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
+ mDumper.addDumpable(tp);
+
// Attach JVMTI agent if this is a debuggable build and the system property is set.
if (Build.IS_DEBUGGABLE) {
// Property is of the form "library_path=parameters".
@@ -2321,7 +2411,11 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
t.traceBegin("StartCarServiceHelperService");
- mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
+ final SystemService cshs = mSystemServiceManager
+ .startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
+ if (cshs instanceof Dumpable) {
+ mDumper.addDumpable((Dumpable) cshs);
+ }
t.traceEnd();
}
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 18bd6b1..b98021b 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
@@ -16,6 +16,7 @@
package com.android.server.job.controllers;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
@@ -27,6 +28,7 @@
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;
+import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
@@ -39,6 +41,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -68,6 +71,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.provider.DeviceConfig;
import android.util.SparseBooleanArray;
import androidx.test.runner.AndroidJUnit4;
@@ -78,6 +82,7 @@
import com.android.server.job.JobServiceContext;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
+import com.android.server.job.controllers.QuotaController.QcConstants;
import com.android.server.job.controllers.QuotaController.TimingSession;
import com.android.server.usage.AppStandbyInternal;
@@ -86,16 +91,19 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.time.Clock;
import java.time.Duration;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
public class QuotaControllerTest {
@@ -113,6 +121,7 @@
private QuotaController.QcConstants mQcConstants;
private int mSourceUid;
private IUidObserver mUidObserver;
+ DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private MockitoSession mMockingSession;
@Mock
@@ -133,6 +142,7 @@
mMockingSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
.startMocking();
@@ -164,6 +174,18 @@
// Used in QuotaController.Handler.
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
+ // Used in QuotaController.QcConstants
+ doAnswer((Answer<Void>) invocationOnMock -> null)
+ .when(() -> DeviceConfig.addOnPropertiesChangedListener(
+ anyString(), any(Executor.class),
+ any(DeviceConfig.OnPropertiesChangedListener.class)));
+ mDeviceConfigPropertiesBuilder =
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+ doAnswer(
+ (Answer<DeviceConfig.Properties>) invocationOnMock
+ -> mDeviceConfigPropertiesBuilder.build())
+ .when(() -> DeviceConfig.getProperties(
+ eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
// in the past, and QuotaController sometimes floors values at 0, so if the test time
@@ -313,6 +335,18 @@
return new TimingSession(start, start + duration, count);
}
+ private void setDeviceConfigLong(String key, long val) {
+ mQuotaController.prepareForUpdatedConstantsLocked();
+ mDeviceConfigPropertiesBuilder.setLong(key, val);
+ mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ }
+
+ private void setDeviceConfigInt(String key, int val) {
+ mQuotaController.prepareForUpdatedConstantsLocked();
+ mDeviceConfigPropertiesBuilder.setInt(key, val);
+ mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ }
+
@Test
public void testSaveTimingSession() {
assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
@@ -858,8 +892,7 @@
advanceElapsedClock(40 * MINUTE_IN_MILLIS);
}
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 0;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -871,8 +904,7 @@
assertEquals(160, mQuotaController.getExecutionStatsLocked(
0, "com.android.test", RARE_INDEX).sessionCountInWindow);
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 500;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -884,8 +916,7 @@
assertEquals(110, mQuotaController.getExecutionStatsLocked(
0, "com.android.test", RARE_INDEX).sessionCountInWindow);
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 1000;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -897,8 +928,8 @@
assertEquals(110, mQuotaController.getExecutionStatsLocked(
0, "com.android.test", RARE_INDEX).sessionCountInWindow);
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 5 * SECOND_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * SECOND_IN_MILLIS);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -910,8 +941,8 @@
assertEquals(70, mQuotaController.getExecutionStatsLocked(
0, "com.android.test", RARE_INDEX).sessionCountInWindow);
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = MINUTE_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ MINUTE_IN_MILLIS);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -923,8 +954,8 @@
assertEquals(20, mQuotaController.getExecutionStatsLocked(
0, "com.android.test", RARE_INDEX).sessionCountInWindow);
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 5 * MINUTE_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * MINUTE_IN_MILLIS);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -936,8 +967,8 @@
assertEquals(10, mQuotaController.getExecutionStatsLocked(
0, "com.android.test", RARE_INDEX).sessionCountInWindow);
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 15 * MINUTE_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 15 * MINUTE_IN_MILLIS);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -951,8 +982,7 @@
// QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
// between an hour and 15 minutes.
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = HOUR_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS);
mQuotaController.invalidateAllExecutionStatsLocked();
assertEquals(0, mQuotaController.getExecutionStatsLocked(
@@ -994,8 +1024,7 @@
// Advance clock so that the working stats shouldn't be the same.
advanceElapsedClock(MINUTE_IN_MILLIS);
// Change frequent bucket size so that the stats need to be recalculated.
- mQcConstants.WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS);
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
@@ -1413,11 +1442,10 @@
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQcConstants.MAX_SESSION_COUNT_RARE = 3;
- mQcConstants.MAX_SESSION_COUNT_FREQUENT = 4;
- mQcConstants.MAX_SESSION_COUNT_WORKING = 5;
- mQcConstants.MAX_SESSION_COUNT_ACTIVE = 6;
- mQcConstants.updateConstants();
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 3);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 4);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 5);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 6);
for (int i = 0; i < 7; ++i) {
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1629,8 +1657,7 @@
final int standbyBucket = RARE_INDEX;
// Prevent timing session throttling from affecting the test.
- mQcConstants.MAX_SESSION_COUNT_RARE = 50;
- mQcConstants.updateConstants();
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50);
// No sessions saved yet.
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -1749,9 +1776,8 @@
public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
// Set rate limiting period different from allowed time to confirm code sets based on
// the former.
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 10 * MINUTE_IN_MILLIS;
- mQcConstants.RATE_LIMITING_WINDOW_MS = 5 * MINUTE_IN_MILLIS;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS);
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int standbyBucket = WORKING_INDEX;
@@ -1790,8 +1816,9 @@
@Test
public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
// Make sure any new value is used correctly.
- mQcConstants.IN_QUOTA_BUFFER_MS *= 2;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS,
+ mQcConstants.IN_QUOTA_BUFFER_MS * 2);
+
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
@@ -1800,8 +1827,9 @@
@Test
public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
// Make sure any new value is used correctly.
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS /= 2;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2);
+
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
@@ -1810,8 +1838,9 @@
@Test
public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
// Make sure any new value is used correctly.
- mQcConstants.MAX_EXECUTION_TIME_MS /= 2;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS,
+ mQcConstants.MAX_EXECUTION_TIME_MS / 2);
+
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
@@ -1820,10 +1849,13 @@
@Test
public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
// Make sure any new value is used correctly.
- mQcConstants.IN_QUOTA_BUFFER_MS *= 2;
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS /= 2;
- mQcConstants.MAX_EXECUTION_TIME_MS /= 2;
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS,
+ mQcConstants.IN_QUOTA_BUFFER_MS * 2);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2);
+ setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS,
+ mQcConstants.MAX_EXECUTION_TIME_MS / 2);
+
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
@@ -1891,27 +1923,30 @@
@Test
public void testConstantsUpdating_ValidValues() {
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
- mQcConstants.IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
- mQcConstants.MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
- mQcConstants.MAX_JOB_COUNT_ACTIVE = 5000;
- mQcConstants.MAX_JOB_COUNT_WORKING = 4000;
- mQcConstants.MAX_JOB_COUNT_FREQUENT = 3000;
- mQcConstants.MAX_JOB_COUNT_RARE = 2000;
- mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * MINUTE_IN_MILLIS;
- mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 500;
- mQcConstants.MAX_SESSION_COUNT_ACTIVE = 500;
- mQcConstants.MAX_SESSION_COUNT_WORKING = 400;
- mQcConstants.MAX_SESSION_COUNT_FREQUENT = 300;
- mQcConstants.MAX_SESSION_COUNT_RARE = 200;
- mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 50;
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 10 * SECOND_IN_MILLIS;
-
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RARE, 2000);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000);
+ setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 200);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RESTRICTED, 100);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 50);
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 10 * SECOND_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS);
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
@@ -1920,6 +1955,8 @@
assertEquals(45 * MINUTE_IN_MILLIS,
mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+ assertEquals(120 * MINUTE_IN_MILLIS,
+ mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]);
assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
@@ -1927,39 +1964,44 @@
assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
+ assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
assertEquals(200, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]);
assertEquals(10 * SECOND_IN_MILLIS,
mQuotaController.getTimingSessionCoalescingDurationMs());
+ assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
}
@Test
public void testConstantsUpdating_InvalidValues() {
// Test negatives/too low.
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
- mQcConstants.IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
- mQcConstants.MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
- mQcConstants.MAX_JOB_COUNT_ACTIVE = -1;
- mQcConstants.MAX_JOB_COUNT_WORKING = 1;
- mQcConstants.MAX_JOB_COUNT_FREQUENT = 1;
- mQcConstants.MAX_JOB_COUNT_RARE = 1;
- mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * SECOND_IN_MILLIS;
- mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 0;
- mQcConstants.MAX_SESSION_COUNT_ACTIVE = -1;
- mQcConstants.MAX_SESSION_COUNT_WORKING = 0;
- mQcConstants.MAX_SESSION_COUNT_FREQUENT = -3;
- mQcConstants.MAX_SESSION_COUNT_RARE = 0;
- mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 0;
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = -1;
-
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RARE, 1);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1);
+ setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 0);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RESTRICTED, -5);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0);
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1);
+ setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(0, mQuotaController.getInQuotaBufferMs());
@@ -1967,6 +2009,7 @@
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+ assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]);
assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
@@ -1974,35 +2017,37 @@
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
+ assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
+ assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]);
assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
+ assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs());
// Invalid configurations.
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 2 * MINUTE_IN_MILLIS;
- mQcConstants.IN_QUOTA_BUFFER_MS = 5 * MINUTE_IN_MILLIS;
-
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 2 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS);
assertTrue(mQuotaController.getInQuotaBufferMs()
<= mQuotaController.getAllowedTimePerPeriodMs());
// Test larger than a day. Controller should cap at one day.
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.RATE_LIMITING_WINDOW_MS = 25 * HOUR_IN_MILLIS;
- mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 25 * HOUR_IN_MILLIS;
-
- mQcConstants.updateConstants();
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 30 * 24 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
@@ -2010,10 +2055,13 @@
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+ assertEquals(7 * 24 * HOUR_IN_MILLIS,
+ mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
assertEquals(15 * MINUTE_IN_MILLIS,
mQuotaController.getTimingSessionCoalescingDurationMs());
+ assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
}
/** Tests that TimingSessions aren't saved when the device is charging. */
@@ -2619,9 +2667,9 @@
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
// Essentially disable session throttling.
- mQcConstants.MAX_SESSION_COUNT_WORKING =
- mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
- mQcConstants.updateConstants();
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, Integer.MAX_VALUE);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ Integer.MAX_VALUE);
final int standbyBucket = WORKING_INDEX;
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
@@ -2671,12 +2719,12 @@
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
// Essentially disable job count throttling.
- mQcConstants.MAX_JOB_COUNT_FREQUENT =
- mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, Integer.MAX_VALUE);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ Integer.MAX_VALUE);
// Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW.
- mQcConstants.MAX_SESSION_COUNT_FREQUENT =
- mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1;
- mQcConstants.updateConstants();
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT,
+ mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1);
final int standbyBucket = FREQUENT_INDEX;
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index 29d3f29..d7fef60 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -34,7 +34,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.location.listeners.ListenerMultiplexer.UpdateServiceLock;
import org.junit.Before;
@@ -59,6 +58,9 @@
void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
+ void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
+
void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
@@ -90,11 +92,42 @@
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
+ mMultiplexer.addListener(1, consumer);
+ mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
+ any(TestListenerRegistration.class));
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
+ any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ assertThat(mMultiplexer.mRegistered).isTrue();
+ assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
+
mMultiplexer.notifyListeners();
verify(consumer).accept(any(TestListenerRegistration.class));
}
@Test
+ public void testReplace() {
+ Consumer<TestListenerRegistration> oldConsumer = mock(Consumer.class);
+ Consumer<TestListenerRegistration> consumer = mock(Consumer.class);
+
+ mMultiplexer.addListener(0, oldConsumer);
+ mInOrder.verify(mCallbacks).onRegister();
+ mInOrder.verify(mCallbacks).onRegistrationAdded(eq(oldConsumer),
+ any(TestListenerRegistration.class));
+ mInOrder.verify(mCallbacks).onActive();
+ mMultiplexer.replaceListener(1, oldConsumer, consumer);
+ mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
+ any(TestListenerRegistration.class));
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
+ any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ assertThat(mMultiplexer.mRegistered).isTrue();
+ assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
+
+ mMultiplexer.notifyListeners();
+ verify(consumer).accept(any(TestListenerRegistration.class));
+ verify(oldConsumer, never()).accept(any(TestListenerRegistration.class));
+ }
+
+ @Test
public void testRemove() {
Consumer<TestListenerRegistration> consumer = mock(Consumer.class);
@@ -319,8 +352,7 @@
}
private static class TestListenerRegistration extends
- RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>,
- ListenerOperation<Consumer<TestListenerRegistration>>> {
+ RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
boolean mActive = true;
@@ -332,9 +364,7 @@
private static class TestMultiplexer extends
ListenerMultiplexer<Consumer<TestListenerRegistration>,
- Consumer<TestListenerRegistration>,
- ListenerOperation<Consumer<TestListenerRegistration>>, TestListenerRegistration,
- Integer> {
+ Consumer<TestListenerRegistration>, TestListenerRegistration, Integer> {
boolean mRegistered;
int mMergedRequest;
@@ -351,7 +381,13 @@
}
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
- addRegistration(consumer, new TestListenerRegistration(request, consumer));
+ putRegistration(consumer, new TestListenerRegistration(request, consumer));
+ }
+
+ public void replaceListener(Integer request, Consumer<TestListenerRegistration> oldConsumer,
+ Consumer<TestListenerRegistration> consumer) {
+ replaceRegistration(oldConsumer, consumer,
+ new TestListenerRegistration(request, consumer));
}
public void removeListener(Consumer<TestListenerRegistration> consumer) {
@@ -422,6 +458,13 @@
}
@Override
+ protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ TestListenerRegistration oldRegistration,
+ TestListenerRegistration newRegistration) {
+ mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
+ }
+
+ @Override
protected void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration) {
mCallbacks.onRegistrationRemoved(consumer, registration);
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index d2d85c8..e76c5a4 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -46,7 +46,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
@@ -158,16 +157,16 @@
}
@Test
- public void testIsPanicButtonGestureEnabled_settingDisabled() {
- withPanicGestureEnabledSettingValue(false);
- assertFalse(mGestureLauncherService.isPanicButtonGestureEnabled(
+ public void testIsEmergencyGestureEnabled_settingDisabled() {
+ withEmergencyGestureEnabledSettingValue(false);
+ assertFalse(mGestureLauncherService.isEmergencyGestureEnabled(
mContext, FAKE_USER_ID));
}
@Test
- public void testIsPanicButtonGestureEnabled_settingEnabled() {
- withPanicGestureEnabledSettingValue(true);
- assertTrue(mGestureLauncherService.isPanicButtonGestureEnabled(
+ public void testIsEmergencyGestureEnabled_settingEnabled() {
+ withEmergencyGestureEnabledSettingValue(true);
+ assertTrue(mGestureLauncherService.isEmergencyGestureEnabled(
mContext, FAKE_USER_ID));
}
@@ -181,10 +180,10 @@
}
@Test
- public void testHandlePanicGesture_userSetupComplete() {
+ public void testHandleEmergencyGesture_userSetupComplete() {
withUserSetupCompleteValue(true);
- assertTrue(mGestureLauncherService.handlePanicButtonGesture());
+ assertTrue(mGestureLauncherService.handleEmergencyGesture());
}
@Test
@@ -196,10 +195,10 @@
}
@Test
- public void testHandlePanicGesture_userSetupNotComplete() {
+ public void testHandleEmergencyGesture_userSetupNotComplete() {
withUserSetupCompleteValue(false);
- assertFalse(mGestureLauncherService.handlePanicButtonGesture());
+ assertFalse(mGestureLauncherService.handleEmergencyGesture());
}
@Test
@@ -223,9 +222,9 @@
}
@Test
- public void testInterceptPowerKeyDown_firstPowerDown_panicGestureNotLaunched() {
- withPanicGestureEnabledSettingValue(true);
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ public void testInterceptPowerKeyDown_firstPowerDown_emergencyGestureNotLaunched() {
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS
+ GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS - 1;
@@ -425,12 +424,12 @@
@Test
public void
- testInterceptPowerKeyDown_fiveInboundPresses_cameraAndPanicEnabled_bothLaunch() {
+ testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() {
withCameraDoubleTapPowerEnableConfigValue(true);
withCameraDoubleTapPowerDisableSettingValue(0);
- withPanicGestureEnabledSettingValue(true);
+ withEmergencyGestureEnabledSettingValue(true);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ mGestureLauncherService.updateEmergencyGestureEnabled();
withUserSetupCompleteValue(true);
// First button press does nothing
@@ -476,7 +475,7 @@
assertEquals(1, tapCounts.get(0).intValue());
assertEquals(2, tapCounts.get(1).intValue());
- // Continue the button presses for the panic gesture.
+ // Continue the button presses for the emergency gesture.
// Presses 3 and 4 should not trigger any gesture
for (int i = 0; i < 2; i++) {
@@ -490,7 +489,7 @@
assertFalse(outLaunched.value);
}
- // Fifth button press should trigger the panic flow
+ // Fifth button press should trigger the emergency flow
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -513,9 +512,9 @@
@Test
public void
- testInterceptPowerKeyDown_fiveInboundPresses_panicGestureEnabled_launchesPanicFlow() {
- withPanicGestureEnabledSettingValue(true);
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ testInterceptPowerKeyDown_fiveInboundPresses_emergencyGestureEnabled_launchesFlow() {
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
withUserSetupCompleteValue(true);
// First button press does nothing
@@ -542,7 +541,7 @@
assertFalse(outLaunched.value);
}
- // Fifth button press should trigger the panic flow
+ // Fifth button press should trigger the emergency flow
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -565,9 +564,9 @@
@Test
public void
- testInterceptPowerKeyDown_tenInboundPresses_panicGestureEnabled_pressesIntercepted() {
- withPanicGestureEnabledSettingValue(true);
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ testInterceptPowerKeyDown_tenInboundPresses_emergencyGestureEnabled_keyIntercepted() {
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
withUserSetupCompleteValue(true);
// First button press does nothing
@@ -594,7 +593,7 @@
assertFalse(outLaunched.value);
}
- // Fifth button press should trigger the panic flow
+ // Fifth button press should trigger the emergency flow
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1128,10 +1127,10 @@
UserHandle.USER_CURRENT);
}
- private void withPanicGestureEnabledSettingValue(boolean enable) {
+ private void withEmergencyGestureEnabledSettingValue(boolean enable) {
Settings.Secure.putIntForUser(
mContentResolver,
- Settings.Secure.PANIC_GESTURE_ENABLED,
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
enable ? 1 : 0,
UserHandle.USER_CURRENT);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 241b5a9..b360ae8 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -81,6 +81,7 @@
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
@@ -128,11 +129,14 @@
sPackageManagerInternal = mock(PackageManagerInternal.class);
doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
.getSystemUiServiceComponent();
- // Remove stale instance of PackageManagerInternal if there is any
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
}
+ @AfterClass
+ public static void tearDownOnce() {
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ }
+
@Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
private Context mContext = getInstrumentation().getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index b2d7177..4f58c87 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -35,6 +35,7 @@
import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerService;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -63,8 +64,6 @@
sPackageManagerInternal = mock(PackageManagerInternal.class);
doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
.getSystemUiServiceComponent();
- // Remove stale instance of PackageManagerInternal if there is any
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
// We need to run with dexmaker share class loader to make use of
@@ -78,13 +77,18 @@
sService.mConstants = new ActivityManagerConstants(sContext, sService,
sContext.getMainThreadHandler());
sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null);
- LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
LocalServices.addService(UsageStatsManagerInternal.class,
mock(UsageStatsManagerInternal.class));
sService.mUsageStatsService = LocalServices.getService(UsageStatsManagerInternal.class);
});
}
+ @AfterClass
+ public static void tearDownOnce() {
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+ }
+
@Before
public void setUpProcess() {
// Need to run with dexmaker share class loader to mock package private class.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c4f7b95..30b1b3e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2814,7 +2814,7 @@
exerciseUserProvisioningTransitions(CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_PROFILE_COMPLETE,
- DevicePolicyManager.STATE_USER_PROFILE_FINALIZED);
+ DevicePolicyManager.STATE_USER_UNMANAGED);
}
public void testSetUserProvisioningState_managedProfileFromSetupWizard_managedProfile()
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 2a9c394..8af7332 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -114,7 +114,7 @@
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
mPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 1385376..37a75e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -123,7 +123,7 @@
hdmiControlService.setCecController(hdmiCecController);
hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
- hdmiControlService.initPortInfo();
+ hdmiControlService.initService();
mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 169f885..6027c3e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -117,7 +117,7 @@
hdmiControlService.setCecController(hdmiCecController);
hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
- hdmiControlService.initPortInfo();
+ hdmiControlService.initService();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 2c42791..bb57a69 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/** Fake {@link NativeWrapper} useful for testing. */
final class FakeNativeWrapper implements NativeWrapper {
@@ -55,6 +56,7 @@
};
private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
+ private final Map<Integer, Boolean> mPortConnectionStatus = new HashMap<>();
private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>();
private int mMyPhysicalAddress = 0;
private HdmiPortInfo[] mHdmiPortInfo = null;
@@ -125,7 +127,12 @@
@Override
public boolean nativeIsConnected(int port) {
- return false;
+ Boolean isConnected = mPortConnectionStatus.get(port);
+ return isConnected == null ? false : isConnected;
+ }
+
+ public void setPortConnectionStatus(int port, boolean connected) {
+ mPortConnectionStatus.put(port, connected);
}
public void onCecMessage(HdmiCecMessage hdmiCecMessage) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 74fd683..433f6e7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -15,13 +15,9 @@
*/
package com.android.server.hdmi;
-import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
-import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE;
-
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
-import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS;
@@ -33,6 +29,7 @@
import android.content.Context;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioManager;
import android.os.Handler;
import android.os.IPowerManager;
@@ -226,7 +223,7 @@
new HdmiPortInfo(
4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
// No TV device interacts with AVR so system audio control won't be turned on here
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -654,75 +651,6 @@
}
@Test
- public void updateCecDevice_deviceNotExists_addDevice() {
- assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
- HdmiDeviceInfo newDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
-
- mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice);
- assertThat(mDeviceInfo).isEqualTo(newDevice);
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
- }
-
- @Test
- public void updateCecDevice_deviceExists_doNothing() {
- mInvokeDeviceEventState = 0;
- HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
- mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
-
- mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(0);
- }
-
- @Test
- public void updateCecDevice_deviceInfoDifferent_updateDevice() {
- assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
- HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
- mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
-
- HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2300, 4, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
-
- mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice);
- assertThat(mDeviceInfo).isEqualTo(differentDevice);
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
- }
-
- @Test
- @Ignore("b/120845532")
- public void handleReportPhysicalAddress_differentPath_addDevice() {
- assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
- HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
- mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
-
- HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_2, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_2));
- HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
- .buildReportPhysicalAddressCommand(
- ADDR_PLAYBACK_2, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK);
- mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress);
-
- mTestLooper.dispatchAll();
- assertThat(mDeviceInfo).isEqualTo(differentDevice);
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
- }
-
- @Test
public void doNotWakeUpOnHotPlug_PlugIn() {
mWokenUp = false;
mHdmiCecLocalDeviceAudioSystem.onHotplug(0, true);
@@ -907,4 +835,42 @@
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
}
+
+ @Test
+ @Ignore("b/151150320")
+ public void oneTouchPlay() {
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn_fromPlayback = HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+ HdmiCecMessage activeSource_fromPlayback = HdmiCecMessageBuilder.buildActiveSource(
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
+ SELF_PHYSICAL_ADDRESS);
+ HdmiCecMessage systemAudioModeRequest_fromPlayback =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
+ ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true);
+ HdmiCecMessage textViewOn_fromAudioSystem = HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+ HdmiCecMessage activeSource_fromAudioSystem = HdmiCecMessageBuilder.buildActiveSource(
+ mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
+ SELF_PHYSICAL_ADDRESS);
+ HdmiCecMessage systemAudioModeRequest_fromAudioSystem =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
+ ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn_fromPlayback);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource_fromPlayback);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+ systemAudioModeRequest_fromPlayback);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn_fromAudioSystem);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource_fromAudioSystem);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+ systemAudioModeRequest_fromAudioSystem);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index ef98b98..440befc 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -25,6 +25,8 @@
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
@@ -125,7 +127,12 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
- mHdmiControlService.initPortInfo();
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mPlaybackPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
@@ -892,6 +899,7 @@
public void handleSetStreamPath_afterHotplug_broadcastsActiveSource() {
mNativeWrapper.onHotplugEvent(1, false);
mNativeWrapper.onHotplugEvent(1, true);
+ mTestLooper.dispatchAll();
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
@@ -967,4 +975,73 @@
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
+
+ @Test
+ public void oneTouchPlay_SendStandbyOnSleepToTv() {
+ mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
+ }
+
+ @Test
+ public void oneTouchPlay_SendStandbyOnSleepBroadcast() {
+ mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).contains(systemAudioModeRequest);
+ }
+
+ @Test
+ public void oneTouchPlay_SendStandbyOnSleepNone() {
+ mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_NONE);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index ce1cdf3..bf4851b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Handler;
import android.os.IPowerManager;
@@ -103,7 +104,11 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
- mHdmiControlService.initPortInfo();
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTvPhysicalAddress = 0x0000;
mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
@@ -119,8 +124,9 @@
@Test
public void onAddressAllocated_invokesDeviceDiscovery() {
+ mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
- mHdmiCecLocalDeviceTv.onAddressAllocated(0, HdmiControlService.INITIATED_BY_BOOT_UP);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 5a05fc6..debf20b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER;
import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE;
import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
@@ -71,6 +72,44 @@
assertMessageValidity("04:90").isEqualTo(ERROR_PARAMETER_SHORT);
}
+ @Test
+ public void isValid_setMenuLanguage() {
+ assertMessageValidity("4F:32:53:50:41").isEqualTo(OK);
+ assertMessageValidity("0F:32:45:4E:47:8C:49:D3:48").isEqualTo(OK);
+
+ assertMessageValidity("40:32:53:50:41").isEqualTo(ERROR_DESTINATION);
+ assertMessageValidity("F0:32").isEqualTo(ERROR_SOURCE);
+ assertMessageValidity("4F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
+ assertMessageValidity("4F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
+ }
+
+ @Test
+ public void isValid_setOsdString() {
+ assertMessageValidity("40:64:80:41").isEqualTo(OK);
+ // Even though the parameter string in this message is longer than 14 bytes, it is accepted
+ // as this parameter might be extended in future versions.
+ assertMessageValidity("04:64:00:4C:69:76:69:6E:67:52:6F:6F:6D:20:54:56:C4").isEqualTo(OK);
+
+ assertMessageValidity("4F:64:40:41").isEqualTo(ERROR_DESTINATION);
+ assertMessageValidity("F0:64:C0:41").isEqualTo(ERROR_SOURCE);
+ assertMessageValidity("40:64:00").isEqualTo(ERROR_PARAMETER_SHORT);
+ // Invalid Display Control
+ assertMessageValidity("40:64:20:4C:69:76").isEqualTo(ERROR_PARAMETER);
+ // Invalid ASCII characters
+ assertMessageValidity("40:64:40:4C:69:7F").isEqualTo(ERROR_PARAMETER);
+ }
+
+ @Test
+ public void isValid_setOsdName() {
+ assertMessageValidity("40:47:4C:69:76:69:6E:67:52:6F:6F:6D:54:56").isEqualTo(OK);
+ assertMessageValidity("40:47:54:56").isEqualTo(OK);
+
+ assertMessageValidity("4F:47:54:56").isEqualTo(ERROR_DESTINATION);
+ assertMessageValidity("F0:47:54:56").isEqualTo(ERROR_SOURCE);
+ assertMessageValidity("40:47").isEqualTo(ERROR_PARAMETER_SHORT);
+ assertMessageValidity("40:47:4C:69:7F").isEqualTo(ERROR_PARAMETER);
+ }
+
private IntegerSubject assertMessageValidity(String message) {
return assertThat(mHdmiCecMessageValidator.isValid(buildMessage(message)));
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
new file mode 100644
index 0000000..080b52b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link HdmiCecNetwork} class.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class HdmiCecNetworkTest {
+
+ private HdmiCecNetwork mHdmiCecNetwork;
+
+ private Context mContext;
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiMhlControllerStub mHdmiMhlControllerStub;
+
+ private HdmiCecController mHdmiCecController;
+ private FakeNativeWrapper mNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+ private HdmiPortInfo[] mHdmiPortInfo;
+ private List<Integer> mDeviceEventListenerStatuses = new ArrayList<>();
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mHdmiControlService = new HdmiControlService(mContext) {
+ @Override
+ void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
+ mDeviceEventListenerStatuses.add(status);
+ }
+ };
+
+ mMyLooper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(mMyLooper);
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(mHdmiControlService,
+ mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService);
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub);
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+
+ mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService,
+ mHdmiCecController, mHdmiMhlControllerStub);
+
+ mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork);
+
+ mHdmiPortInfo = new HdmiPortInfo[5];
+ mHdmiPortInfo[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
+ mHdmiPortInfo[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
+ mHdmiPortInfo[2] =
+ new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ mHdmiPortInfo[3] =
+ new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
+ mHdmiPortInfo[4] =
+ new HdmiPortInfo(5, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(mHdmiPortInfo);
+ mHdmiCecNetwork.initPortInfo();
+ }
+
+ @Test
+ public void initializeNetwork_verifyPortInfo() {
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.getPortInfo()).hasSize(mHdmiPortInfo.length);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathExists_weAreNonTv() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(1);
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2234)).isEqualTo(2);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathExists_weAreSourceDevice() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x0000)).isEqualTo(5);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathExists_weAreTv() {
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(3);
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x3234)).isEqualTo(4);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathInvalid() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x1000)).isEqualTo(
+ Constants.INVALID_PORT_ID);
+ }
+
+ @Test
+ public void localDevices_verifyOne_tv() {
+ mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
+ new HdmiCecLocalDeviceTv(mHdmiControlService));
+
+ assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+ assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+ HdmiCecLocalDeviceTv.class);
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNotNull();
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNull();
+ }
+
+ @Test
+ public void localDevices_verifyOne_playback() {
+ mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ new HdmiCecLocalDevicePlayback(mHdmiControlService));
+
+ assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+ assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+ HdmiCecLocalDevicePlayback.class);
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNotNull();
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNull();
+ }
+
+ @Test
+ public void cecDevices_tracking_logicalAddressOnly() throws Exception {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_logicalAddressOnly_doesntNotifyAgain() throws Exception {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportPhysicalAddress() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ physicalAddress);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_updateDeviceInfo_sameDoesntNotify() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+
+
+ // ADD for logical address first detected
+ // UPDATE for updating device with physical address
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportPowerStatus() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int powerStatus = HdmiControlManager.POWER_STATUS_ON;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportOsdName() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ String osdName = "Test Device";
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, osdName));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportVendorId() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int vendorId = 1234;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesDeviceInfo() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+ int powerStatus = HdmiControlManager.POWER_STATUS_ON;
+ String osdName = "Test Device";
+ int vendorId = 1234;
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, osdName));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(physicalAddress);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesPhysicalAddress() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int initialPhysicalAddress = 0x1000;
+ int updatedPhysicalAddress = 0x2000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ initialPhysicalAddress, type));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ updatedPhysicalAddress, type));
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
+
+ // ADD for logical address first detected
+ // UPDATE for updating device with physical address
+ // UPDATE for updating device with new physical address
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesPowerStatus() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int powerStatus = HdmiControlManager.POWER_STATUS_ON;
+ int updatedPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, updatedPowerStatus));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(updatedPowerStatus);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesOsdName() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ String osdName = "Test Device";
+ String updatedOsdName = "Different";
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, osdName));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, updatedOsdName));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(updatedOsdName);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesVendorId() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int vendorId = 1234;
+ int updatedVendorId = 12345;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, updatedVendorId));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(updatedVendorId);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_clearDevices() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ mHdmiCecNetwork.clearDeviceList();
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).isEmpty();
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
index c4068d3..74a0052 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
@@ -21,11 +21,13 @@
import static junit.framework.Assert.assertEquals;
+import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.Looper;
import android.os.test.TestLooper;
+import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -44,6 +46,8 @@
@RunWith(JUnit4.class)
public class HdmiControlServiceBinderAPITest {
+ private Context mContext;
+
private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice {
private boolean mCanGoToStandby;
@@ -111,8 +115,12 @@
@Before
public void SetUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ // Some tests expect no logical addresses being allocated at the beginning of the test.
+ setHdmiControlEnabled(false);
+
mHdmiControlService =
- new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ new HdmiControlService(mContext) {
@Override
void sendCecCommand(HdmiCecMessage command) {
switch (command.getOpcode()) {
@@ -164,7 +172,7 @@
mHdmiPortInfo[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
mResult = -1;
mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
@@ -183,6 +191,7 @@
assertEquals(mResult, -1);
assertThat(mPlaybackDevice.isActiveSource()).isFalse();
+ setHdmiControlEnabled(true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
@@ -192,6 +201,8 @@
@Test
public void oneTouchPlay_addressAllocated() {
+ setHdmiControlEnabled(true);
+
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
@@ -204,4 +215,10 @@
assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
assertThat(mPlaybackDevice.isActiveSource()).isTrue();
}
+
+ private void setHdmiControlEnabled(boolean enabled) {
+ int value = enabled ? 1 : 0;
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED,
+ value);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 2f48b5e..8f631a3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -176,7 +176,7 @@
mHdmiPortInfo[3] =
new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -206,29 +206,6 @@
}
@Test
- public void pathToPort_pathExists_weAreNonTv() {
- mNativeWrapper.setPhysicalAddress(0x2000);
- mHdmiControlService.initPortInfo();
- assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1);
- assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2);
- }
-
- @Test
- public void pathToPort_pathExists_weAreTv() {
- mNativeWrapper.setPhysicalAddress(0x0000);
- mHdmiControlService.initPortInfo();
- assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3);
- assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4);
- }
-
- @Test
- public void pathToPort_pathInvalid() {
- mNativeWrapper.setPhysicalAddress(0x2000);
- mHdmiControlService.initPortInfo();
- assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID);
- }
-
- @Test
public void initialPowerStatus_normalBoot_isTransientToStandby() {
assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index 6be28d9..0e4bfab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -90,8 +90,6 @@
break;
case Constants.MESSAGE_INITIATE_ARC:
break;
- default:
- throw new IllegalArgumentException("Unexpected message");
}
}
@@ -159,6 +157,14 @@
return -1;
}
};
+
+ Looper looper = mTestLooper.getLooper();
+ hdmiControlService.setIoLooper(looper);
+ HdmiCecController.NativeWrapper nativeWrapper = new FakeNativeWrapper();
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ hdmiControlService, nativeWrapper, hdmiControlService.getAtomWriter());
+ hdmiControlService.setCecController(hdmiCecController);
+ hdmiControlService.initService();
mHdmiCecLocalDeviceAudioSystem =
new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
@Override
@@ -181,8 +187,6 @@
}
};
mHdmiCecLocalDeviceAudioSystem.init();
- Looper looper = mTestLooper.getLooper();
- hdmiControlService.setIoLooper(looper);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 72afca0..9b25d0d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -45,6 +45,7 @@
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.TestPackageParser2;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import org.junit.Before;
import org.junit.Test;
@@ -288,6 +289,31 @@
assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isFalse();
}
+ /**
+ * registerApkInApex method checks if the prefix of base apk path contains the apex package
+ * name. When an apex package name is a prefix of another apex package name, e.g,
+ * com.android.media and com.android.mediaprovider, then we need to ensure apk inside apex
+ * mediaprovider does not get registered under apex media.
+ */
+ @Test
+ public void testRegisterApkInApexDoesNotRegisterSimilarPrefix() throws RemoteException {
+ when(mApexService.getActivePackages()).thenReturn(createApexInfo(true, true));
+ final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
+ assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
+
+ AndroidPackage fakeApkInApex = mock(AndroidPackage.class);
+ when(fakeApkInApex.getBaseApkPath()).thenReturn("/apex/" + TEST_APEX_PKG + "randomSuffix");
+ when(fakeApkInApex.getPackageName()).thenReturn("randomPackageName");
+
+ when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true));
+ mApexManager.scanApexPackagesTraced(mPackageParser2,
+ ParallelPackageParser.makeExecutorService());
+
+ assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+ mApexManager.registerApkInApex(fakeApkInApex);
+ assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+ }
+
private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) {
File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
ApexInfo apexInfo = new ApexInfo();
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
index 86758f1..40d959d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -222,4 +222,18 @@
assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
assertFalse(mIncrementalStates.isLoading());
}
+
+ /**
+ * Test startability transitions if app crashes or anrs
+ */
+ @Test
+ public void testStartableTransition_AppCrashOrAnr() {
+ mIncrementalStates.onCrashOrAnr();
+ assertFalse(mIncrementalStates.isStartable());
+ mIncrementalStates.setProgress(1.0f);
+ assertTrue(mIncrementalStates.isStartable());
+ mIncrementalStates.onCrashOrAnr();
+ // Test that if fully loaded, app remains startable even if it has crashed
+ assertTrue(mIncrementalStates.isStartable());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 9be3164..f1d3e18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -35,6 +35,7 @@
import android.app.ActivityOptions;
import android.app.ActivityOptions.SourceInfo;
+import android.app.WaitResult;
import android.content.Intent;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -167,10 +168,15 @@
@Test
public void testOnActivityLaunchFinished() {
+ // Assume that the process is started (ActivityBuilder has mocked the returned value of
+ // ATMS#getProcessController) but the activity has not attached process.
+ mTopActivity.app = null;
onActivityLaunched(mTopActivity);
notifyTransitionStarting(mTopActivity);
- notifyWindowsDrawn(mTopActivity);
+ final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
+ assertWithMessage("Warm launch").that(info.getLaunchState())
+ .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
verifyOnActivityLaunchFinished(mTopActivity);
verifyNoMoreInteractions(mLaunchObserver);
@@ -204,7 +210,7 @@
notifyActivityLaunching(noDrawnActivity.intent);
notifyActivityLaunched(START_SUCCESS, noDrawnActivity);
- noDrawnActivity.destroyIfPossible("test");
+ noDrawnActivity.mVisibleRequested = false;
mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity));
@@ -225,6 +231,8 @@
assertWithMessage("Record start source").that(info.sourceType)
.isEqualTo(SourceInfo.TYPE_LAUNCHER);
assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10);
+ assertWithMessage("Hot launch").that(info.getLaunchState())
+ .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
verifyOnActivityLaunchFinished(mTopActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index caf8a72..b632331 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -245,9 +245,8 @@
.setStack(rootHomeTask)
.setCreateTask(true)
.build();
- final Task secondaryStack = (Task) WindowContainer.fromBinder(
- mAtm.mTaskOrganizerController.createRootTask(rootHomeTask.getDisplayId(),
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token.asBinder());
+ final Task secondaryStack = mAtm.mTaskOrganizerController.createRootTask(
+ rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
rootHomeTask.reparent(secondaryStack, POSITION_TOP);
assertEquals(secondaryStack, rootHomeTask.getParent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e478819..3720e520 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1022,7 +1022,7 @@
assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
// Move activity to split-screen-primary stack and make sure it has the focus.
- TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayId());
+ TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM);
top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 64a05bb..747fb89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static android.view.DragEvent.ACTION_DRAG_STARTED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -82,6 +84,8 @@
@RunWith(WindowTestRunner.class)
public class DragDropControllerTests extends WindowTestsBase {
private static final int TIMEOUT_MS = 3000;
+ private static final int TEST_UID = 12345;
+
private TestDragDropController mTarget;
private WindowState mWindow;
private IBinder mToken;
@@ -278,6 +282,42 @@
return clipData;
}
+ @Test
+ public void testValidateAppShortcutArguments() {
+ final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ });
+ try {
+ final ClipData clipData = new ClipData(
+ new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_SHORTCUT }),
+ new ClipData.Item(new Intent()));
+
+ session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID);
+ fail("Expected failure without shortcut id");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ }
+
+ @Test
+ public void testValidateAppTaskArguments() {
+ final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ });
+ try {
+ final ClipData clipData = new ClipData(
+ new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_TASK }),
+ new ClipData.Item(new Intent()));
+
+ session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID);
+ fail("Expected failure without task id");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ }
+
private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
startDrag(flags, data, () -> {
mTarget.handleMotionEvent(false, dropX, dropY);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index dffa790..d68dde5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -28,6 +28,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.Task.ActivityState.STOPPED;
@@ -35,8 +36,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doCallRealMethod;
import android.app.ActivityManager;
@@ -628,7 +632,6 @@
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
final Rect displayBounds = mActivity.mDisplayContent.getBounds();
- final Rect newTaskBounds = mTask.getBounds();
final Rect newActivityBounds = mActivity.getBounds();
assertTrue(displayBounds.width() < displayBounds.height());
@@ -673,6 +676,91 @@
activityBounds.width());
}
+ @Test
+ public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInTaskLetterbox() {
+ // Set up a display in landscape and ignoring orientation request.
+ setUpDisplaySizeWithApp(2800, 1400);
+ final DisplayContent display = mActivity.mDisplayContent;
+ display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ // Portrait fixed app without max aspect.
+ prepareUnresizable(0, SCREEN_ORIENTATION_PORTRAIT);
+
+ assertTrue(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Launch another portrait fixed app.
+ spyOn(mTask);
+ setBooted(display.mWmService.mAtmService);
+ final ActivityRecord newActivity = new ActivityBuilder(display.mWmService.mAtmService)
+ .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setTask(mTask)
+ .build();
+
+ // Update with new activity requested orientation and recompute bounds with no previous
+ // size compat cache.
+ verify(mTask).onDescendantOrientationChanged(any(), same(newActivity));
+ verify(mTask).computeFullscreenBounds(any(), any(), any(), anyInt());
+ verify(newActivity).clearSizeCompatMode(false /* recomputeTask */);
+
+ final Rect displayBounds = display.getBounds();
+ final Rect taskBounds = mTask.getBounds();
+ final Rect newActivityBounds = newActivity.getBounds();
+
+ // Task and app bounds should be 700x1400 with the ratio as the display.
+ assertTrue(mTask.isTaskLetterboxed());
+ assertFalse(newActivity.inSizeCompatMode());
+ assertEquals(taskBounds, newActivityBounds);
+ assertEquals(displayBounds.height(), taskBounds.height());
+ assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
+ taskBounds.width());
+ }
+
+ @Test
+ public void testDisplayIgnoreOrientationRequest_newLaunchedMaxAspectApp() {
+ // Set up a display in landscape and ignoring orientation request.
+ setUpDisplaySizeWithApp(2800, 1400);
+ final DisplayContent display = mActivity.mDisplayContent;
+ display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ // Portrait fixed app without max aspect.
+ prepareUnresizable(0, SCREEN_ORIENTATION_PORTRAIT);
+
+ assertTrue(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Launch another portrait fixed app with max aspect ratio as 1.3.
+ spyOn(mTask);
+ setBooted(display.mWmService.mAtmService);
+ final ActivityRecord newActivity = new ActivityBuilder(display.mWmService.mAtmService)
+ .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setMaxAspectRatio(1.3f)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setTask(mTask)
+ .build();
+
+ // Update with new activity requested orientation and recompute bounds with no previous
+ // size compat cache.
+ verify(mTask).onDescendantOrientationChanged(any(), same(newActivity));
+ verify(mTask).computeFullscreenBounds(any(), any(), any(), anyInt());
+ verify(newActivity).clearSizeCompatMode(false /* recomputeTask */);
+
+ final Rect displayBounds = display.getBounds();
+ final Rect taskBounds = mTask.getBounds();
+ final Rect newActivityBounds = newActivity.getBounds();
+
+ // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
+ assertTrue(mTask.isTaskLetterboxed());
+ assertEquals(displayBounds.height(), taskBounds.height());
+ assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio),
+ taskBounds.width());
+
+ // App bounds should be fullscreen in Task bounds.
+ assertFalse(newActivity.inSizeCompatMode());
+ assertEquals(taskBounds, newActivityBounds);
+ }
+
private static WindowState addWindowToActivity(ActivityRecord activity) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index ace0400..3203ccb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -23,6 +23,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -181,17 +182,27 @@
}
@Test
- public void testSwitchUser() {
+ public void testEnsureActivitiesVisible() {
final Task rootTask = createTaskStackOnDisplay(mDisplayContent);
- final Task childTask = createTaskInStack(rootTask, 0 /* userId */);
- final Task leafTask1 = createTaskInStack(childTask, 10 /* userId */);
- final Task leafTask2 = createTaskInStack(childTask, 0 /* userId */);
- assertEquals(1, rootTask.getChildCount());
- assertEquals(leafTask2, childTask.getTopChild());
+ final Task leafTask1 = createTaskInStack(rootTask, 0 /* userId */);
+ final Task leafTask2 = createTaskInStack(rootTask, 0 /* userId */);
+ final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, leafTask1);
+ final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, leafTask2);
- doReturn(true).when(leafTask1).showToCurrentUser();
- rootTask.switchUser(10);
- assertEquals(1, rootTask.getChildCount());
- assertEquals(leafTask1, childTask.getTopChild());
+ // Check visibility of occluded tasks
+ doReturn(false).when(leafTask1).shouldBeVisible(any());
+ doReturn(true).when(leafTask2).shouldBeVisible(any());
+ rootTask.ensureActivitiesVisible(
+ null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+ assertFalse(activity1.isVisible());
+ assertTrue(activity2.isVisible());
+
+ // Check visibility of not occluded tasks
+ doReturn(true).when(leafTask1).shouldBeVisible(any());
+ doReturn(true).when(leafTask2).shouldBeVisible(any());
+ rootTask.ensureActivitiesVisible(
+ null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+ assertTrue(activity1.isVisible());
+ assertTrue(activity2.isVisible());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7a1f65a..b4480ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -479,21 +479,22 @@
@Test
public void testCreateDeleteRootTasks() {
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
+
+ Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ dc, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+ RunningTaskInfo info1 = task1.getTaskInfo();
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
info1.configuration.windowConfiguration.getWindowingMode());
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
- RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ dc, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info2 = task2.getTaskInfo();
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
info2.configuration.windowConfiguration.getWindowingMode());
assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType);
- DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
List<Task> infos = getTasksCreatedByOrganizer(dc);
assertEquals(2, infos.size());
@@ -521,8 +522,9 @@
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info1 = task.getTaskInfo();
final Task stack = createTaskStackOnDisplay(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
@@ -579,8 +581,9 @@
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info1 = task.getTaskInfo();
lastReportedTiles.clear();
called[0] = false;
@@ -640,10 +643,13 @@
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+ Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+ RunningTaskInfo info1 = task1.getTaskInfo();
+ Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info2 = task2.getTaskInfo();
final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */).size();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1eb7cbe..62f04a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -973,8 +973,9 @@
final int taskId = mTaskId >= 0 ? mTaskId : mTaskDisplayArea.getNextStackId();
if (mParentTask == null) {
task = mTaskDisplayArea.createStackUnchecked(
- mWindowingMode, mActivityType, taskId, mOnTop, mActivityInfo,
- mIntent, false /* createdByOrganizer */);
+ mWindowingMode, mActivityType, taskId, mOnTop, mActivityInfo, mIntent,
+ false /* createdByOrganizer */, false /* deferTaskAppear */,
+ null /* launchCookie */);
} else {
task = new Task(mSupervisor.mService, taskId, mActivityInfo,
mIntent /*intent*/, mVoiceSession, null /*_voiceInteractor*/,
@@ -1016,20 +1017,17 @@
// moves everything to secondary. Most tests expect this since sysui usually does it.
boolean mMoveToSecondaryOnEnter = true;
int mDisplayId;
- TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
+ TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) {
mService = service;
- mDisplayId = displayId;
+ mDisplayId = display.mDisplayId;
mService.mTaskOrganizerController.registerTaskOrganizer(this);
- WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask(
- displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
- mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
- WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask(
- displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
- mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask();
+ mPrimary = mService.mTaskOrganizerController.createRootTask(
+ display, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+ mSecondary = mService.mTaskOrganizerController.createRootTask(
+ display, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);;
}
TestSplitOrganizer(ActivityTaskManagerService service) {
- this(service,
- service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay().mDisplayId);
+ this(service, service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay());
}
public void setMoveToSecondaryOnEnter(boolean move) {
mMoveToSecondaryOnEnter = move;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 82da447..ae485d5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -32,6 +32,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -1605,6 +1606,30 @@
}
/**
+ * Returns whether the caller has {@link InCallService} access for companion apps.
+ *
+ * A companion app is an app associated with a physical wearable device via the
+ * {@link android.companion.CompanionDeviceManager} API.
+ *
+ * @return {@code true} if the caller has {@link InCallService} access for
+ * companion app; {@code false} otherwise.
+ */
+ public boolean hasCompanionInCallServiceAccess() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecomService().hasCompanionInCallServiceAccess(
+ mContext.getOpPackageName());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling hasCompanionInCallServiceAccess().", e);
+ if (!isSystemProcess()) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns whether there is an ongoing call originating from a managed
* {@link ConnectionService}. An ongoing call can be in dialing, ringing, active or holding
* states.
@@ -2416,6 +2441,10 @@
}
}
+ private boolean isSystemProcess() {
+ return Process.myUid() == Process.SYSTEM_UID;
+ }
+
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
return mTelecomServiceOverride;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 7c6f1df..e636b93 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -179,6 +179,11 @@
boolean isInCall(String callingPackage, String callingFeatureId);
/**
+ * @see TelecomServiceImpl#hasCompanionInCallServiceAccess
+ */
+ boolean hasCompanionInCallServiceAccess(String callingPackage);
+
+ /**
* @see TelecomServiceImpl#isInManagedCall
*/
boolean isInManagedCall(String callingPackage, String callingFeatureId);
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 63e0e5a..a58b6d8 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -747,6 +747,7 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
+ method public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -780,6 +781,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+ method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
@@ -819,6 +821,11 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -851,6 +858,9 @@
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
+ field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 9223842..f8a200a 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -369,7 +369,6 @@
* Get the 5G NR connection state.
*
* @return the 5G NR connection state.
- * @hide
*/
public @NRState int getNrState() {
return mNrState;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 20eeb1d..aa68dcf 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13672,6 +13672,149 @@
}
}
+ /**
+ * No error. Operation succeeded.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0;
+
+ /**
+ * NR Dual connectivity enablement is not supported.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1;
+
+ /**
+ * Radio is not available.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2;
+
+ /**
+ * Internal Radio error.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3;
+
+ /**
+ * Currently in invalid state. Not able to process the request.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ENABLE_NR_DUAL_CONNECTIVITY"}, value = {
+ ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS,
+ ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED,
+ ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE,
+ ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE,
+ ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR})
+ public @interface EnableNrDualConnectivityResult {}
+
+ /**
+ * Enable NR dual connectivity. Enabled state does not mean dual connectivity
+ * is active. It means device is allowed to connect to both primary and secondary.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1;
+
+ /**
+ * Disable NR dual connectivity. Disabled state does not mean the secondary cell is released.
+ * Modem will release it only if the current bearer is released to avoid radio link failure.
+ * @hide
+ */
+ @SystemApi
+ public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2;
+
+ /**
+ * Disable NR dual connectivity and force the secondary cell to be released if dual connectivity
+ * was active. This will result in radio link failure.
+ * @hide
+ */
+ @SystemApi
+ public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "NR_DUAL_CONNECTIVITY_" }, value = {
+ NR_DUAL_CONNECTIVITY_ENABLE,
+ NR_DUAL_CONNECTIVITY_DISABLE,
+ NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NrDualConnectivityState {
+ }
+
+ /**
+ * Enable/Disable E-UTRA-NR Dual Connectivity.
+ *
+ * @param nrDualConnectivityState expected NR dual connectivity state
+ * This can be passed following states
+ * <ol>
+ * <li>Enable NR dual connectivity {@link #NR_DUAL_CONNECTIVITY_ENABLE}
+ * <li>Disable NR dual connectivity {@link #NR_DUAL_CONNECTIVITY_DISABLE}
+ * <li>Disable NR dual connectivity and force secondary cell to be released
+ * {@link #NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE}
+ * </ol>
+ * @return operation result.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @SystemApi
+ public @EnableNrDualConnectivityResult int setNrDualConnectivityState(
+ @NrDualConnectivityState int nrDualConnectivityState) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.setNrDualConnectivityState(getSubId(), nrDualConnectivityState);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setNrDualConnectivityState RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+
+ return ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE;
+ }
+
+ /**
+ * Is E-UTRA-NR Dual Connectivity enabled.
+ * @return true if dual connectivity is enabled else false. Enabled state does not mean dual
+ * connectivity is active. It means the device is allowed to connect to both primary and
+ * secondary cell.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @SystemApi
+ public boolean isNrDualConnectivityEnabled() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isNrDualConnectivityEnabled(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "isNRDualConnectivityEnabled RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
private static class DeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0d8351d..d33835f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2216,4 +2216,20 @@
* does not exist on the SIM card.
*/
List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId);
+
+ /**
+ * Enable/Disable E-UTRA-NR Dual Connectivity
+ * @return operation result. See TelephonyManager.EnableNrDualConnectivityResult for
+ * details
+ * @param subId the id of the subscription
+ * @param enable enable/disable dual connectivity
+ */
+ int setNrDualConnectivityState(int subId, int nrDualConnectivityState);
+
+ /**
+ * Is E-UTRA-NR Dual Connectivity enabled
+ * @param subId the id of the subscription
+ * @return true if dual connectivity is enabled else false
+ */
+ boolean isNrDualConnectivityEnabled(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index d524299..953a292 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -494,6 +494,8 @@
int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
int RIL_REQUEST_GET_BARRING_INFO = 211;
int RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION = 212;
+ int RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY = 213;
+ int RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED = 214;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 9924931..6cc2e22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -100,10 +100,8 @@
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
- navBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
- statusBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
imeLayerBecomesInvisible(bugId = 141458352)
imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 46f584b..8d9881e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -106,10 +106,8 @@
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
- navBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
- statusBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
imeLayerBecomesInvisible(bugId = 153739621)
imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 1194933..ad23d9f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -100,10 +100,8 @@
configuration.endRotation)
statusBarLayerRotatesScales(Surface.ROTATION_0,
configuration.endRotation)
- navBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
- statusBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
wallpaperLayerBecomesInvisible(testApp)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 136be29..5886a61 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -104,10 +104,8 @@
configuration.endRotation)
statusBarLayerRotatesScales(Surface.ROTATION_0,
configuration.endRotation)
- navBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
- statusBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
wallpaperLayerBecomesInvisible(testApp)
}
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index 4195df7..20f564e 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -27,7 +27,6 @@
android:process=":externalProcess">
</activity>
-
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.test.input"
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index e169312..11a83eb 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -30,6 +30,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
@@ -359,6 +360,33 @@
}
@Test
+ public void testOemPrivate() {
+ NetworkCapabilities nc = new NetworkCapabilities();
+ // By default OEM_PRIVATE is neither in the unwanted or required lists and the network is
+ // not restricted.
+ assertFalse(nc.hasUnwantedCapability(NET_CAPABILITY_OEM_PRIVATE));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+ nc.maybeMarkCapabilitiesRestricted();
+ assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ // Adding OEM_PRIVATE to capability list should make network restricted.
+ nc.addCapability(NET_CAPABILITY_OEM_PRIVATE);
+ nc.addCapability(NET_CAPABILITY_INTERNET); // Combine with unrestricted capability.
+ nc.maybeMarkCapabilitiesRestricted();
+ assertTrue(nc.hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ // Now let's make request for OEM_PRIVATE network.
+ NetworkCapabilities nr = new NetworkCapabilities();
+ nr.addCapability(NET_CAPABILITY_OEM_PRIVATE);
+ nr.maybeMarkCapabilitiesRestricted();
+ assertTrue(nr.satisfiedByNetworkCapabilities(nc));
+
+ // Request fails for network with the default capabilities.
+ assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
+ }
+
+ @Test
public void testUnwantedCapabilities() {
NetworkCapabilities network = new NetworkCapabilities();
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index da64402..28ff606 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -15,7 +15,7 @@
# limitations under the License.
"""Generate API lists for non-SDK API enforcement."""
import argparse
-from collections import defaultdict
+from collections import defaultdict, namedtuple
import functools
import os
import re
@@ -54,16 +54,21 @@
FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
ALL_FLAGS_SET = set(ALL_FLAGS)
-# Suffix used in command line args to express that only known and
-# otherwise unassigned entries should be assign the given flag.
+# Option specified after one of FLAGS_API_LIST to indicate that
+# only known and otherwise unassigned entries should be assign the
+# given flag.
# For example, the max-target-P list is checked in as it was in P,
# but signatures have changes since then. The flag instructs this
# script to skip any entries which do not exist any more.
-FLAG_IGNORE_CONFLICTS_SUFFIX = "-ignore-conflicts"
+FLAG_IGNORE_CONFLICTS = "ignore-conflicts"
-# Suffix used in command line args to express that all apis within a given set
-# of packages should be assign the given flag.
-FLAG_PACKAGES_SUFFIX = "-packages"
+# Option specified after one of FLAGS_API_LIST to express that all
+# apis within a given set of packages should be assign the given flag.
+FLAG_PACKAGES = "packages"
+
+# Option specified after one of FLAGS_API_LIST to indicate an extra
+# tag that should be added to the matching APIs.
+FLAG_TAG = "tag"
# Regex patterns of fields/methods used in serialization. These are
# considered public API despite being hidden.
@@ -86,6 +91,17 @@
IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
+class StoreOrderedOptions(argparse.Action):
+ """An argparse action that stores a number of option arguments in the order that
+ they were specified.
+ """
+ def __call__(self, parser, args, values, option_string = None):
+ items = getattr(args, self.dest, None)
+ if items is None:
+ items = []
+ items.append([option_string.lstrip('-'), values])
+ setattr(args, self.dest, items)
+
def get_args():
"""Parses command line arguments.
@@ -98,17 +114,19 @@
help='CSV files to be merged into output')
for flag in ALL_FLAGS:
- ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX
- packages_flag = flag + FLAG_PACKAGES_SUFFIX
- parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE',
- help='lists of entries with flag "' + flag + '"')
- parser.add_argument('--' + ignore_conflicts_flag, dest=ignore_conflicts_flag, nargs='*',
- default=[], metavar='TXT_FILE',
- help='lists of entries with flag "' + flag +
- '". skip entry if missing or flag conflict.')
- parser.add_argument('--' + packages_flag, dest=packages_flag, nargs='*',
- default=[], metavar='TXT_FILE',
- help='lists of packages to be added to ' + flag + ' list')
+ parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE',
+ action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"')
+ parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0,
+ action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned '
+ 'entries should be assign the given flag. Must follow a list of entries and applies '
+ 'to the preceding such list.')
+ parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0,
+ action=StoreOrderedOptions, help='Indicates that the previous list of entries '
+ 'is a list of packages. All members in those packages will be given the flag. '
+ 'Must follow a list of entries and applies to the preceding such list.')
+ parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1,
+ action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. '
+ 'Must follow a list of entries and applies to the preceding such list.')
return parser.parse_args()
@@ -170,11 +188,10 @@
def _check_entries_set(self, keys_subset, source):
assert isinstance(keys_subset, set)
assert keys_subset.issubset(self._dict_keyset), (
- "Error processing: {}\n"
- "The following entries were unexpected:\n"
+ "Error: {} specifies signatures not present in code:\n"
"{}"
"Please visit go/hiddenapi for more information.").format(
- source, "".join(map(lambda x: " " + str(x), keys_subset - self._dict_keyset)))
+ source, "".join(map(lambda x: " " + str(x) + "\n", keys_subset - self._dict_keyset)))
def _check_flags_set(self, flags_subset, source):
assert isinstance(flags_subset, set)
@@ -258,7 +275,7 @@
flags.append(FLAG_SDK)
self._dict[csv[0]].update(flags)
- def assign_flag(self, flag, apis, source="<unknown>"):
+ def assign_flag(self, flag, apis, source="<unknown>", tag = None):
"""Assigns a flag to given subset of entries.
Args:
@@ -278,11 +295,44 @@
# Iterate over the API subset, find each entry in dict and assign the flag to it.
for api in apis:
self._dict[api].add(flag)
+ if tag:
+ self._dict[api].add(tag)
+
+
+FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
+
+def parse_ordered_flags(ordered_flags):
+ r = []
+ currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None
+ for flag_value in ordered_flags:
+ flag, value = flag_value[0], flag_value[1]
+ if flag in ALL_FLAGS_SET:
+ if currentflag:
+ r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+ ignore_conflicts, packages, tag = False, False, None
+ currentflag = flag
+ file = value
+ else:
+ if currentflag is None:
+ raise argparse.ArgumentError('--%s is only allowed after one of %s' % (
+ flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
+ if flag == FLAG_IGNORE_CONFLICTS:
+ ignore_conflicts = True
+ elif flag == FLAG_PACKAGES:
+ packages = True
+ elif flag == FLAG_TAG:
+ tag = value[0]
+
+
+ if currentflag:
+ r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+ return r
def main(argv):
# Parse arguments.
args = vars(get_args())
+ flagfiles = parse_ordered_flags(args['ordered_flags'])
# Initialize API->flags dictionary.
flags = FlagsDict()
@@ -300,28 +350,28 @@
flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION))
# (2) Merge text files with a known flag into the dictionary.
- for flag in ALL_FLAGS:
- for filename in args[flag]:
- flags.assign_flag(flag, read_lines(filename), filename)
+ for info in flagfiles:
+ if (not info.ignore_conflicts) and (not info.packages):
+ flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag)
# Merge text files where conflicts should be ignored.
# This will only assign the given flag if:
# (a) the entry exists, and
# (b) it has not been assigned any other flag.
# Because of (b), this must run after all strict assignments have been performed.
- for flag in ALL_FLAGS:
- for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]:
- valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename))
- flags.assign_flag(flag, valid_entries, filename)
+ for info in flagfiles:
+ if info.ignore_conflicts:
+ valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file))
+ flags.assign_flag(info.flag, valid_entries, filename, info.tag)
# All members in the specified packages will be assigned the appropriate flag.
- for flag in ALL_FLAGS:
- for filename in args[flag + FLAG_PACKAGES_SUFFIX]:
- packages_needing_list = set(read_lines(filename))
+ for info in flagfiles:
+ if info.packages:
+ packages_needing_list = set(read_lines(info.file))
should_add_signature_to_list = lambda sig,lists: extract_package(
sig) in packages_needing_list and not lists
valid_entries = flags.filter_apis(should_add_signature_to_list)
- flags.assign_flag(flag, valid_entries)
+ flags.assign_flag(info.flag, valid_entries, info.file, info.tag)
# Mark all remaining entries as blocked.
flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))