diff options
338 files changed, 9539 insertions, 3816 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 9604466a0edd..754c4e94e73a 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -77,6 +77,7 @@ stubs_defaults { "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 6c14233dba13..34e82b0ce45e 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 class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService && !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 c7cc2f03b062..00dbb8235d29 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 @@ public final class JobStatus { } 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 c06e19cbf687..b7ace70f0cd4 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.AlarmManager; 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.Looper; 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { } @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 @@ public final class QuotaController extends StateController { } } + @Override + public void prepareForUpdatedConstantsLocked() { + mQcConstants.mShouldReevaluateConstraints = false; + mQcConstants.mRateLimitingConstantsUpdated = false; + mQcConstants.mExecutionPeriodConstantsUpdated = false; + } + + @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 extends ContentObserver { - private ContentResolver mResolver; - private final KeyValueListParser mParser = new KeyValueListParser(','); - - 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"; + 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 @@ public final class QuotaController extends StateController { /** 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); - } - - 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(); + 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; + } } - @VisibleForTesting - void updateConstants() { - synchronized (mLock) { - boolean changed = false; - - 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))); + private void updateExecutionPeriodConstantsLocked() { + if (mExecutionPeriodConstantsUpdated) { + return; + } + mExecutionPeriodConstantsUpdated = true; + + // 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); + + 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; - if (changed) { - // Update job bookkeeping out of band. - JobSchedulerBackgroundThread.getHandler().post(() -> { - synchronized (mLock) { - invalidateAllExecutionStatsLocked(); - maybeUpdateAllConstraintsLocked(); - } - }); - } + // 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 @@ public final class QuotaController extends StateController { } @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 71c759931f57..56b30907b2a1 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 @@ package com.android.server.job.controllers; 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 abstract class StateController { 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 813631e28a88..b3c9a9aca444 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -35,7 +35,6 @@ java_library { libs: [ "framework_media_annotation", ], - static_libs: [ "exoplayer2-extractor" ], @@ -111,10 +110,33 @@ java_sdk_library { 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 e4b5d19e67c9..045b4136a710 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.LinkedHashMap; 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 @@ public final class MediaParser { // 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 @@ public final class MediaParser { 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 @@ public final class MediaParser { @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 @@ public final class MediaParser { 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 @@ public final class MediaParser { 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 @@ public final class MediaParser { 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 @@ public final class MediaParser { } } if (mExtractor == null) { - throw UnrecognizedInputFormatException.createForExtractors(mParserNamesPool); + UnrecognizedInputFormatException exception = + UnrecognizedInputFormatException.createForExtractors(mParserNamesPool); + mLastObservedExceptionName = exception.getClass().getName(); + throw exception; } return true; } @@ -1223,8 +1250,13 @@ public final class MediaParser { 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 @@ public final class MediaParser { * 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 @@ public final class MediaParser { 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 @@ public final class MediaParser { @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 @@ public final class MediaParser { @Override public void format(Format format) { + mTrackFormats.put(mTrackIndex, format); mOutputConsumer.onTrackDataFound( mTrackIndex, new TrackData( @@ -2031,6 +2129,20 @@ public final class MediaParser { 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 @@ public final class MediaParser { } } + // 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 @@ public final class MediaParser { // 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 000000000000..7fc4628984f5 --- /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 1581960852d8..b880f85e3830 100644 --- a/api/current.txt +++ b/api/current.txt @@ -97,6 +97,7 @@ package android { 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 @@ package android.hardware.camera2 { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.service.controls.templates { 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 @@ package android.service.controls.templates { 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 @@ package android.telecom { 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 @@ package android.telephony { 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 @@ package android.view { } 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 @@ package android.view { 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 @@ package android.view { 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 @@ package android.view { 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 @@ package android.widget { 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 @@ package android.widget { 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 de62fd80c92c..c932718bc55c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -403,6 +403,7 @@ package android.app { 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 @@ package android.app.admin { 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 @@ package android.net { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 59020068e342..82838ea605ff 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -84,7 +84,7 @@ package android.app { 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 @@ package android.app { 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 @@ package android.content.pm { 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 @@ package android.content.pm { 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 @@ package android.view { } 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 @@ package android.window { 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 0440d1a95065..c0b40931f1e6 100644 --- a/api/test-lint-baseline.txt +++ b/api/test-lint-baseline.txt @@ -539,6 +539,8 @@ InternalField: android.telephony.ims.ImsConferenceState#mParticipants: KotlinOperator: android.os.WorkSource#get(int): +KotlinOperator: android.util.SparseArrayMap#get(int, K): + KotlinOperator: android.util.SparseArrayMap#get(int, String): @@ -594,17 +596,17 @@ MinMaxConstant: android.view.autofill.AutofillManager#MAX_TEMP_AUGMENTED_SERVICE 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#forceStopPackage(String) paramet 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.LauncherApps#LauncherApps(android.content 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 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 @@ SetterReturnsThis: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyS +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 c9356c55e6a1..1cfe9ee784f3 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -59,6 +59,7 @@ import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enu 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 @@ message Atom { 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 @@ message Atom { 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 @@ message PrivacyIndicatorsInteracted { 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 @@ message MediametricsExtractorReported { } /** + * 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 @@ message OutgoingSms { } /** - * 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 @@ message AirplaneMode { } /** - * 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 @@ message ModemRestart { } /** - * 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 @@ message ModemRestart { * 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 @@ message CarrierIdMismatchEvent { // 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 @@ message HdmiCecMessageReported { // 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 3d57cfe318c5..22fdf1604435 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 @@ ValueMetricProducer::~ValueMetricProducer() { } } +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 67de214e655c..ebd8fecd55d0 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -47,7 +47,7 @@ struct PastValueBucket { // - 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 @@ private: // 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 @@ private: 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 b6e5d8846fd9..335f7753e5e3 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 @@ bool determineAllMetricUpdateStatuses(const StatsdConfig& config, return false; } } - // TODO: determine update status for value metrics. return true; } @@ -644,7 +643,7 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 } 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 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 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 } 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 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const 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 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const 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 34d7e9c7de9e..3f1c5326b569 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 @@ bool updateMetrics( 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 b7dc2c7fd0de..8fc039a7d6b3 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 @@ optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata( 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 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t // 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 6d1e6dde7e89..e4585cd578f8 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 @@ optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata( 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 0066030ade54..4fa9bf6ffc01 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 @@ TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { /*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 @@ TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) { /*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 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { {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 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { /*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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { *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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { // 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { /*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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { // 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { 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 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { 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 250f2f0b2dc9..74da95f02c76 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4812,13 +4812,14 @@ public class ActivityManager { /** * 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 f3b37891876d..3bcb87aa73f2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -865,6 +865,8 @@ public class AppOpsManager { @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 @@ public class AppOpsManager { /** @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 class AppOpsManager { 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 @@ public class AppOpsManager { OP_MANAGE_EXTERNAL_STORAGE, OP_INTERACT_ACROSS_PROFILES, OP_LOADER_USAGE_STATS, + OP_MANAGE_ONGOING_CALLS, }; /** @@ -1688,6 +1700,7 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { OPSTR_PHONE_CALL_MICROPHONE, OPSTR_PHONE_CALL_CAMERA, OPSTR_RECORD_AUDIO_HOTWORD, + OPSTR_MANAGE_ONGOING_CALLS, }; /** @@ -1907,6 +1921,7 @@ public class AppOpsManager { "PHONE_CALL_MICROPHONE", "PHONE_CALL_CAMERA", "RECORD_AUDIO_HOTWORD", + "MANAGE_ONGOING_CALLS", }; /** @@ -2018,6 +2033,7 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { null, // PHONE_CALL_MICROPHONE null, // PHONE_CALL_MICROPHONE null, // RECORD_AUDIO_HOTWORD + null, // MANAGE_ONGOING_CALLS }; /** @@ -2239,6 +2256,7 @@ public class AppOpsManager { null, // PHONE_CALL_MICROPHONE null, // PHONE_CALL_CAMERA null, // RECORD_AUDIO_HOTWORD + null, // MANAGE_ONGOING_CALLS }; /** @@ -2348,6 +2366,7 @@ public class AppOpsManager { 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 @@ public class AppOpsManager { 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 357b26c3083d..c0e3019f4619 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -689,6 +689,8 @@ interface IActivityManager { /** * 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 861e4378c68e..37c4c92d0635 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -130,6 +130,7 @@ public final class PendingIntent implements Parcelable { FLAG_UPDATE_CURRENT, FLAG_IMMUTABLE, FLAG_MUTABLE, + FLAG_MUTABLE_UNAUDITED, Intent.FILL_IN_ACTION, Intent.FILL_IN_DATA, @@ -205,6 +206,13 @@ public final class PendingIntent implements Parcelable { 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 @@ public final class PendingIntent implements Parcelable { * 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 @@ public final class PendingIntent implements Parcelable { * 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 849f679c9439..5caf3057c840 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -24,6 +24,8 @@ import android.content.ComponentName; 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 class TaskInfo { 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 @@ public class TaskInfo { /** @hide */ public void addLaunchCookie(IBinder cookie) { + if (cookie == null || launchCookies.contains(cookie)) return; launchCookies.add(cookie); } @@ -256,6 +273,8 @@ public class TaskInfo { 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 @@ public class TaskInfo { dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); dest.writeBinderList(launchCookies); + dest.writeTypedObject(letterboxActivityBounds, flags); + dest.writeTypedObject(positionInParent, flags); } @Override @@ -306,6 +327,9 @@ public class TaskInfo { + " 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 4ae1670e9041..c4af4edb467b 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -796,6 +796,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** * 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 3cc7f1e5df42..4c541b3f6b76 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -55,22 +55,6 @@ public final class DeviceAdminInfo implements Parcelable { 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 054e8429ff86..8d4de2675472 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -525,7 +525,7 @@ public class DevicePolicyManager { * @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 class DevicePolicyManager { 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 @@ public class DevicePolicyManager { 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 dedfce6f6b96..f9e6308515cf 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 class ClipDescription implements Parcelable { 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 @@ public class ClipDescription implements Parcelable { } /** + * 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 d9ecf46069cd..d688614f6caa 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 @@ interface ILauncherApps { 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 c32d34457889..106021e81ddc 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -794,5 +794,7 @@ interface IPackageManager { 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 b7af397cd36a..2909d66d72ff 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 @@ public class LauncherApps { } /** + * 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 3fb9a9e924e7..602bd6c939ba 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -63,6 +63,7 @@ import android.net.wifi.WifiManager; 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 @@ public abstract class PackageManager { } /** + * 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 802655b0d364..2b689899af01 100644 --- a/core/java/android/hardware/biometrics/BiometricTestSession.java +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -46,7 +46,7 @@ public class BiometricTestSession implements AutoCloseable { mContext = context; mTestSession = testSession; mTestedUsers = new ArraySet<>(); - enableTestHal(true); + setTestHalEnabled(true); } /** @@ -56,12 +56,12 @@ public class BiometricTestSession implements AutoCloseable { * 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 @@ public class BiometricTestSession implements AutoCloseable { @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 6112f17949d7..fa7a62c53531 100644 --- a/core/java/android/hardware/biometrics/ITestSession.aidl +++ b/core/java/android/hardware/biometrics/ITestSession.aidl @@ -27,7 +27,7 @@ interface ITestSession { // 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 8cfa0866f13a..228617c74388 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -190,6 +190,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { } } + private final String mCameraId; @UnsupportedAppUsage private final CameraMetadataNative mResults; private final CaptureRequest mRequest; @@ -202,7 +203,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { throw new AssertionError("Results must not be empty"); } setNativeInstance(mResults); + mCameraId = cameraId; mRequest = parent; mSequenceId = extras.getRequestId(); mFrameNumber = extras.getFrameNumber(); @@ -251,12 +253,27 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { } 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 7cc2623a29ba..da65f71ce02c 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -70,10 +70,10 @@ public final class TotalCaptureResult extends CaptureResult { * @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 @@ public final class TotalCaptureResult extends CaptureResult { 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 48ec3fd808fe..819d966e3bfe 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -1980,7 +1980,7 @@ public class CameraDeviceImpl extends CameraDevice // 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 @@ public class CameraDeviceImpl extends CameraDevice 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 @@ public class CameraDeviceImpl extends CameraDevice 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 @@ public class CameraDeviceImpl extends CameraDevice 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 1d8b2a123c6a..eb2ff88ec1b2 100644 --- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java @@ -334,7 +334,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession // 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 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession 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 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession 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 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession 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 be33f4edb5d1..12ddc628f4bd 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -170,6 +170,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_MCX, NET_CAPABILITY_PARTIAL_CONNECTIVITY, NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NET_CAPABILITY_OEM_PRIVATE, }) public @interface NetCapability { } @@ -345,8 +346,15 @@ public final class NetworkCapabilities implements Parcelable { */ 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 @@ public final class NetworkCapabilities implements Parcelable { * {@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 @@ public final class NetworkCapabilities implements Parcelable { 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 c39fd4d1bc43..8c98362fa909 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -44,7 +44,7 @@ import android.util.proto.ProtoOutputStream; * 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 8cdcd49cb2cc..13b30f43ff7a 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -908,7 +908,11 @@ public class RecoverySystem { 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 @@ public class RecoverySystem { 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 c3b6d8e2cfe3..105ffaa4718e 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.telephony.PhoneNumberUtils; 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 class CallLog { */ 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 class CallLog { 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 class CallLog { 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 @@ public class CallLog { * @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 @@ public class CallLog { 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 @@ public class CallLog { 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 @@ public class CallLog { } 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 a4c8114159d3..9c41886850fe 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8380,18 +8380,19 @@ public final class Settings { 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 @@ public final class Settings { "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 final class Settings { 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 6bd376a19fc5..c5277ee0ed5e 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -206,8 +206,8 @@ public abstract class ControlsProviderService extends Service { 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 @@ public abstract class ControlsProviderService extends Service { 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 @@ public abstract class ControlsProviderService extends Service { 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 @@ public abstract class ControlsProviderService extends Service { + "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 e592fad394b8..3902d6af69e7 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.CallSuper; 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 @@ public abstract class ControlTemplate { TYPE_NO_TEMPLATE, TYPE_TOGGLE, TYPE_RANGE, + TYPE_THUMBNAIL, TYPE_TOGGLE_RANGE, TYPE_TEMPERATURE, TYPE_STATELESS @@ -105,6 +107,11 @@ public abstract class ControlTemplate { 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 @@ public abstract class ControlTemplate { } /** + * Call to prepare values for Binder transport. + * + * @hide + */ + public void prepareTemplateForBinder(@NonNull Context context) {} + + /** * * @param bundle * @return @@ -187,6 +201,8 @@ public abstract class ControlTemplate { 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 000000000000..a7c481e38e55 --- /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 3396bceb75d0..a942f6ce2879 100644 --- a/core/java/android/text/Editable.java +++ b/core/java/android/text/Editable.java @@ -137,7 +137,7 @@ extends CharSequence, GetChars, Spannable, Appendable } /** - * 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 ee98b65d2444..a7d20b56eca3 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -64,7 +64,7 @@ public class EventLog { 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 43a8992aa74e..d226f6048471 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -755,6 +755,8 @@ interface IWindowManager /** * 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 12ea936e96a1..726176568605 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; 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 @@ import java.util.ArrayList; 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 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { } private void updateTouchListener() { - if (mExpandClickListener == null && mFeedbackListener == null) { + if (mExpandClickListener == null) { setOnTouchListener(null); return; } @@ -274,15 +243,6 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { } /** - * 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 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { mTouchRects.clear(); addRectAroundView(mIcon); mExpandButtonRect = addRectAroundView(mExpandButton); - mFeedbackRect = addRectAroundView(mFeedbackIcon); addWidthRect(); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @@ -401,11 +349,11 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { 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 000000000000..9443ccfc7553 --- /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 a217ff642ab7..d74938c1d1fd 100644 --- a/core/java/android/view/OnReceiveContentCallback.java +++ b/core/java/android/view/OnReceiveContentCallback.java @@ -19,9 +19,7 @@ package android.view; 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 com.android.internal.util.Preconditions; 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 @@ import java.util.Set; * * <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 @@ public interface OnReceiveContentCallback<T extends View> { * @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 667f0b9511d0..ac628e145eee 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -47,6 +47,7 @@ import android.annotation.UiThread; 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.graphics.drawable.GradientDrawable; 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 android.widget.ScrollBarDrawable; 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 9bc07702feee..f1005e91868e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1758,7 +1758,6 @@ public final class ViewRootImpl implements ViewParent, destroySurface(); } } - scheduleConsumeBatchedInputImmediately(); } @@ -8314,11 +8313,8 @@ public final class ViewRootImpl implements ViewParent, @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 2cab32b48793..d540059f993a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -4069,11 +4069,12 @@ public interface WindowManager extends ViewManager { /** * 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 7dfae002b554..4292a800afe1 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -280,9 +280,9 @@ public final class WindowManagerImpl implements WindowManager { } @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 e0711132f459..093dfb4e196e 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -22,6 +22,7 @@ import android.annotation.CallSuper; 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 @@ public class BaseInputConnection implements InputConnection { } /** - * 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 @@ public class BaseInputConnection implements InputConnection { 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 3fc0f4efd608..5280a48596b4 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8747,12 +8747,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * <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 d7c95b7eae86..7ed70ec18a7b 100644 --- a/core/java/android/widget/TextViewOnReceiveContentCallback.java +++ b/core/java/android/widget/TextViewOnReceiveContentCallback.java @@ -20,12 +20,12 @@ import static android.content.ContentResolver.SCHEME_CONTENT; 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 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac @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 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac 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 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac } } } - return true; + return didFirst; } private static void replaceSelection(@NonNull Editable editable, @@ -160,7 +155,7 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac @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 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac * 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 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac 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 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac } /** - * 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 3a84c1f98ce6..4a43a438b69d 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -40,7 +40,7 @@ interface ITaskOrganizerController { 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 6c739bed35a4..ad48a9f9f776 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; 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 @@ public class TaskOrganizer extends WindowOrganizer { @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 000000000000..7df346139a4d --- /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 37a9f50dec89..2d2c8accc039 100644 --- a/core/proto/android/app/enums.proto +++ b/core/proto/android/app/enums.proto @@ -210,4 +210,5 @@ enum AppOpEnum { 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 16a691c9c4ec..9291a90574cd 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -510,7 +510,7 @@ message GlobalSettingsProto { 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 83c53915694a..d934b82e36c3 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -219,8 +219,10 @@ message SecureSettingsProto { 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 a264f18f921c..a49a1adcc619 100644 --- a/core/proto/android/server/fingerprint.proto +++ b/core/proto/android/server/fingerprint.proto @@ -66,3 +66,36 @@ message PerformanceStatsProto { // 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 000000000000..0ae87ee1d89b --- /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 01954510ee08..95a7414e12ea 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 88493c9505ed..d11875e0f890 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 000000000000..27fab859a045 --- /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 ba21082da910..70cfaa7a741d 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 e3ddbd8d25a2..f8266ba177ca 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 e6ebd4b543c5..84e7d42f2ed9 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 94397d661af0..697f7e0e898a 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 f4ebe2f9a755..63e464240630 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.Context; 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.os.RemoteException; 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 @@ public class ControlProviderServiceTest { mPendingIntent = new PendingIntent(mIIntentSender); - mControlsProviderService = new FakeControlsProviderService(); + mControlsProviderService = new FakeControlsProviderService( + InstrumentationRegistry.getInstrumentation().getContext()); mControlsProvider = IControlsProvider.Stub.asInterface( mControlsProviderService.onBind(intent)); } @@ -134,7 +140,8 @@ public class ControlProviderServiceTest { 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 @@ public class ControlProviderServiceTest { .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 @@ public class ControlProviderServiceTest { 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 @@ public class ControlProviderServiceTest { 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 87dc1b7c83d5..91a3ba7d0e74 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 @@ public class ControlTemplateTest { } @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 5112326ea0b6..ef659af6c570 100644 --- a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java +++ b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java @@ -66,8 +66,8 @@ import org.mockito.Mockito; * {@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 @@ public class TextViewOnReceiveContentCallbackTest { // 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 @@ public class TextViewOnReceiveContentCallbackTest { 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 a430dcdda247..e51bf5e4e1f6 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 f8db4477d87a..bee9f4163b04 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 5bd693a9311e..fc0a76e8d286 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.TASK_LISTENER_TYPE_FULLSCR 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 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { 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 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @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 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @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 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { } @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 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { 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 000000000000..9010c2088c34 --- /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 cbc1c8d6d310..d4ff275d426d 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_FREEFORM; 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 static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG 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.internal.protolog.common.ProtoLog; 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 class ShellTaskOrganizer extends TaskOrganizer { 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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 class ShellTaskOrganizer extends TaskOrganizer { } } + 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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 8f8b98bbfbae..bf5b1d8a4bcc 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.PRIVATE_FLAG_NO_MOVE_ANIMA 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 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange private static final String TAG = DragAndDropController.class.getSimpleName(); + private final Context mContext; private final DisplayController mDisplayController; private SplitScreen mSplitScreen; @@ -76,7 +95,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange 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 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange 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 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange 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 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange 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 3ded4091ec11..59c79bafd46c 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.app.PictureInPictureParams; 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 @@ import java.util.function.Consumer; */ 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 @@ public interface Pip { } /** - * 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 @@ public interface Pip { } /** - * 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 @@ public interface Pip { } /** - * 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 9b4524a12161..a05aac9dfe3e 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 b27af656f209..b9422ce153ac 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 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac /** * 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 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac /** * Gets the {@link android.media.session.MediaController} for the PIPed activity. */ - public MediaController getMediaController() { + MediaController getMediaController() { return mPipMediaController; } @@ -655,7 +655,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac * 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 ff617ed466d1..eb82357f2dea 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 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor 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 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor 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 2b14e8bf88d6..c6496ad6246f 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 @@ public class DividerView extends FrameLayout implements OnTouchListener, private boolean mAdjustedForIme; private DividerState mState; - private SplitScreenTaskOrganizer mTiles; + private SplitScreenTaskListener mTiles; boolean mFirstLayout = true; int mDividerPositionX; int mDividerPositionY; @@ -354,7 +354,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, 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 3c0f93906795..7d5e1a84a38d 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 @@ public class SplitDisplayLayout { private static final int DIVIDER_WIDTH_INACTIVE_DP = 4; - SplitScreenTaskOrganizer mTiles; + SplitScreenTaskListener mTiles; DisplayLayout mDisplayLayout; Context mContext; @@ -62,7 +62,7 @@ public class SplitDisplayLayout { 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 43e4d62baaf6..69d428a3ae1f 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 @@ public class SplitScreenController implements SplitScreen, 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 @@ public class SplitScreenController implements SplitScreen, 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 @@ public class SplitScreenController implements SplitScreen, // 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 @@ public class SplitScreenController implements SplitScreen, } 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 index f763d6d714c4..191a317452e3 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_PRIMAR 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 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { 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 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { @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 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { 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 47e7c99d2268..c51bbeb7b6c2 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 @@ class WindowManagerProxy { 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 @@ class WindowManagerProxy { * * @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 @@ class WindowManagerProxy { * 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 000000000000..45d4d5d347dd --- /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 07a6bda239c7..35a2293cbf13 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.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 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 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 static org.mockito.Mockito.verify; 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.ITaskOrganizer; 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 @@ public class ShellTaskOrganizerTests { 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 42cf53b44f1e..3905e0b2b878 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -52,9 +52,11 @@ interface ILocationManager 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 @@ interface ILocationManager 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 2738ff4ff38c..0ff0a723237b 100644 --- a/location/java/android/location/LocationListener.java +++ b/location/java/android/location/LocationListener.java @@ -19,12 +19,11 @@ package android.location; 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 @@ import android.os.Bundle; * <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 ac775ca05cab..3493693ac67e 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -216,15 +216,15 @@ public class LocationManager { * 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 @@ public class LocationManager { 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 class LocationManager { 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 @@ public class LocationManager { @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 @@ public class LocationManager { 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 7a3a4b23d532..32ac374b33c2 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 @@ public abstract class LocationProviderBase { 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 @@ public abstract class LocationProviderBase { 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 @@ public abstract class LocationProviderBase { 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 @@ public abstract class LocationProviderBase { 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 98ca2f9c4253..4b208ce1ec37 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -362,28 +362,34 @@ public final class MediaCas implements AutoCloseable { @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 a98ea695eaf1..2d25061164c4 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -97,6 +97,7 @@ package android { 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 @@ package android.hardware.camera2 { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.service.controls.templates { 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 @@ package android.service.controls.templates { 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 @@ package android.telecom { 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 @@ package android.telephony { 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 @@ package android.view { } 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 @@ package android.view { 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 @@ package android.view { 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 @@ package android.view { 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 @@ package android.widget { 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 @@ package android.widget { 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 79c82695e911..b2d13f908b61 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -403,6 +403,7 @@ package android.app { 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 @@ package android.app.admin { 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 @@ package android.net { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 b377ec47f6c6..cf288d74d61c 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 4f2bba8aa1de..8281631312b7 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 edc36215bcce..d8710555149b 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 398f5c38c03e..89c9eb4ee258 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 6c1979f7a95a..6aee01321ade 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 4e9794855e47..fde42732283f 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 7dfab54b8add..25f284515b9f 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 119f2d7bf793..588771e41740 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 b1e722e95c58..cbd469b68a62 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 dd4472f06aa3..7657e32b0223 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 6c08aa56bc7e..120929e34347 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 66f8d18472c9..9b24fa488923 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 b3e358fbb6ef..8eb76c20d8a2 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 b3e358fbb6ef..8eb76c20d8a2 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 b3e358fbb6ef..8eb76c20d8a2 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 b3e358fbb6ef..8eb76c20d8a2 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 eaf6f51d3092..37a568bf3e5a 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 6a5f8ce43318..16aba86f3c3f 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 c43d7e54d559..8aad2cad9cf2 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 ad82d5fc230b..14ec9df45c2d 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 ef37b654bde9..3f53b1145b96 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 10bb0c5b782c..79b53f6daa2c 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 bebd3f441410..b1905490f229 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 3d498d2f9ca7..5a905a000c42 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 95454a53709f..83321fd630b9 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 f3aaf63eac18..872fc69d8cfb 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 b63ba8b8ed60..63328f370ed3 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 e2a2c6bf459c..778f6954da3d 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 0a70d261b77d..386d79e0e88b 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 ea6b031c72b9..5a927aa0928a 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 ecbcd5d9dadd..41d7ad4f70ee 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 fe182a3b7251..f419cac56d48 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 14486758dcd1..9cf056fdf7c6 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 0fef7e55f27e..7d62c62a8abe 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 a4cf78709515..2dd1b66f1778 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 7b9a093d7f39..709cfe572ce9 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 b570c5c6b81e..17a24667b547 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 c66b34ffb9c1..dd9225a1aa0b 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 2bf19e03b82a..bc94a5104c6b 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 1cae1e907193..a47ad5909f12 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 62b8bf8d98eb..cb7c8b9f1575 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 3e7ad63ae2d9..cd2ae973a078 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 45921d26172e..33bcd275117d 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 1a43d9c7cdef..0bb683b0a58d 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 4f3922b373b5..4e7ca39a2b12 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 5b6166feba0f..0b2856f0d7b2 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 d79f2b1d10f4..4765f71b8b61 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 dd8c1892b63e..52b90f1b5b96 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 c7f5ecf00707..2dffa17e8f1c 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 0712fb82f7fd..a7c44d2522d6 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 60fd4fef41c0..1a4e71d9eea8 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 a043d24789a9..330ba2f0bdbd 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 e14f64a7bca8..6391d28e9568 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 8f98983877d9..b100a5d4cf5d 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 6b471845b657..b67002bec7d4 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 18ea40131817..d19e1583664b 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 878a1c1aa425..a5fb5b4f6ad6 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 905dd445eeab..8a942d6af080 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 3cb6098a3f1b..be03373c48a1 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 de52edee1621..a82a2f856bd4 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 8f3012b5b4a5..dacf605888f5 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 5b0e3b3cf19c..89d8cd201e5f 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 81fa01c16058..36bf694f30ab 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 b7031c698815..391513f1b57a 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 471e4591265a..398d1f5ce29e 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 26bdddc750cd..f15320fd1dcd 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 e7ca871337fa..a91f48c8b378 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 268243fea6f7..7aa611606274 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 9dc0f1a03d3a..c062463905d7 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 8845ff71c1bb..2dd33d827324 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 02f4457bffdb..9aa7cc4ae29f 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 @@ public class DeviceChooserActivity extends Activity { 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 c4b36fb1ab29..b9e30fb950c6 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 @@ public class SecureSettings { 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 7f694ad5d375..721bf730a343 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 @@ public class SecureSettingsValidators { 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 94de48595b0d..1a2c2c8ac2e9 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 @@ class SettingsProtoDumpUtil { 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 @@ class SettingsProtoDumpUtil { 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 10d2eab167e7..6b4629a3f981 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -309,7 +309,6 @@ public class SettingsBackupTest { 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 407104504132..ddd0dac0e9db 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 a40bfffaabd7..e4de6f91769c 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 2bc6c90cefd3..68ef1a31eb4e 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 fe38ea09888a..afa0759e90a2 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 cd958854f53e..1ad7a91e551d 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 4b1ed0a25c90..1ab776b2a0a8 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 000000000000..22ffd2819c48 --- /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 000000000000..0b1141ee9b30 --- /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 000000000000..d24c779b1416 --- /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 326c2aa37175..7b9ebc0d4656 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 @@ public class TaskInfoCompat { 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 class TaskInfoCompat { 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 9f7358bf94ff..8bcffc8ece1a 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 @@ public class BadgedImageView extends ImageView { 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 0714c5eb4fa4..1201d42b1fc5 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.ArrayList; 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 @@ public class BubbleStackView extends FrameLayout 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 @@ public class BubbleStackView extends FrameLayout 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 31e1ca839e5d..c410b8267dd4 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.SpringAnimation; 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 com.google.android.collect.Sets; 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 @@ public class StackAnimationController extends 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 @@ public class StackAnimationController extends /** 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 class StackAnimationController extends } } + 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 @@ public class StackAnimationController extends 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 e3ee2a10821b..fff185b99a1e 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 @@ public class LogModule { 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 000000000000..e96e532f94bf --- /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 2ddcc5aa77fe..1a9dd712bd0e 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 @@ public class PeopleSpaceUtils { /** Converts {@code drawable} to a {@link Bitmap}. */ public static Bitmap convertDrawableToBitmap(Drawable drawable) { + if (drawable == null) { + return null; + } + if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); + 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 + ); } - // 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); + 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 000000000000..44f173bc5175 --- /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 85801f9e7206..aa98b61ff947 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 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { 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 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { 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 093925a2664a..c68c30632b6c 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 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R 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 3da1363f2a56..7359e79b26f5 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 @@ import com.android.systemui.R 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 dc5ba693a658..87ffbd465109 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.SysUISingleton 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 @@ class PrivacyItemController @Inject constructor( @Background private val bgExecutor: Executor, private val deviceConfigProxy: DeviceConfigProxy, private val userTracker: UserTracker, + private val logger: PrivacyLogger, dumpManager: DumpManager ) : Dumpable { @@ -158,6 +160,7 @@ class PrivacyItemController @Inject constructor( } val userId = UserHandle.getUserId(uid) if (userId in currentUserIds) { + logger.logUpdatedItemFromAppOps(code, uid, packageName, active) update(false) } } @@ -194,6 +197,7 @@ class PrivacyItemController @Inject constructor( bgExecutor.execute { if (updateUsers) { currentUserIds = userTracker.userProfiles.map { it.id } + logger.logCurrentProfilesChanged(currentUserIds) } updateListAndNotifyChanges.run() } @@ -260,6 +264,8 @@ class PrivacyItemController @Inject constructor( } 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 000000000000..c88676e713b3 --- /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 7e2433a1fd33..8e0e4ac7c8ef 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 @@ public class QSContainerImpl extends FrameLayout { 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 44f847c34e2b..32904a21cd3c 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.OngoingPrivacyChip; 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mUserTracker = userTracker; mLifecycle = new LifecycleRegistry(mLifecycleOwner); mHeaderQsPanelController = quickQSPanelController; + mPrivacyLogger = privacyLogger; mQSCarrierGroupController = qsCarrierGroupControllerBuilder .setQSCarrierGroup(mView.findViewById(R.id.carrier_group)) @@ -323,6 +327,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 c0061ad97293..b2ebf3f700b9 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 597658276d49..25c8e7feb9b2 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 @@ public class NotificationHeaderUtil { 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 160b6f78d9a3..71e1d12b610e 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 @@ public class NotificationContentView extends FrameLayout { ? 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 0fc4c42599cf..5aeacaba6f64 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.app.Notification; 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 com.android.systemui.statusbar.notification.row.ExpandableNotificationRow 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @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 6ed092f33d95..d53724159244 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.dagger.qualifiers.UiBackground; 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 @@ public class PhoneStatusBarPolicy 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 @@ public class PhoneStatusBarPolicy @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 @@ public class PhoneStatusBarPolicy 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 @@ public class PhoneStatusBarPolicy || 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 c84589a9e142..4552026ced4b 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.row.wrapper.NotificationViewW 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @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 1c682e3bb7dc..409d1361223c 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 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { 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 ead2fc1cd9dc..cff1c9d086ea 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 @@ public abstract class WMShellBaseModule { @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 cd94f8444c45..c401fab1e1bc 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.internal.config.sysui.SystemUiDeviceConfigFlags 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 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { 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 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { 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 16a11050f9bb..3e834986e383 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.SysuiTestCase 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 @@ class PrivacyItemControllerTest : SysuiTestCase() { 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 @@ class PrivacyItemControllerTest : SysuiTestCase() { executor, deviceConfigProxy, userTracker, - dumpManager - ) + logger, + dumpManager) } @Before @@ -300,6 +304,45 @@ class PrivacyItemControllerTest : SysuiTestCase() { 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 226995198843..26b5d26387b4 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.demomode.DemoModeController 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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { 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 000000000000..cb0a20bdf0e8 --- /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 02bab9ba353e..5765c01c43f3 100644 --- a/packages/Tethering/tests/integration/Android.bp +++ b/packages/Tethering/tests/integration/Android.bp @@ -79,6 +79,7 @@ android_test { // 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 index 7ed89632a861..c99ff7f81877 100644 --- a/packages/Tethering/tests/unit/jarjar-rules.txt +++ b/packages/Tethering/tests/jarjar-rules.txt @@ -10,7 +10,10 @@ rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.t 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 04137145a2b0..ef556cf92392 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -68,7 +68,6 @@ java_defaults { "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 @@ android_test { "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 4c8925bd4828..70cf04522c45 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 class PackageManagerInternal { 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 000000000000..426352b250f8 --- /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 afea9760078a..85d77f246bbb 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3998,13 +3998,11 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 000000000000..d2bd66f59b62 --- /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 20f68da1592e..d4e912b65e17 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -75,9 +75,9 @@ public class GestureLauncherService extends SystemService { @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 @@ public class GestureLauncherService extends SystemService { 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 @@ public class GestureLauncherService extends SystemService { "GestureLauncherService"); updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); - updatePanicButtonGestureEnabled(); + updateEmergencyGestureEnabled(); mUserId = ActivityManager.getCurrentUser(); mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); @@ -197,7 +197,7 @@ public class GestureLauncherService extends SystemService { 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 @@ public class GestureLauncherService extends SystemService { } @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 @@ public class GestureLauncherService extends SystemService { } /** - * 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 @@ public class GestureLauncherService extends SystemService { return false; } boolean launchCamera = false; - boolean launchPanic = false; + boolean launchEmergencyGesture = false; boolean intercept = false; long powerTapInterval; synchronized (this) { @@ -428,15 +428,15 @@ public class GestureLauncherService extends SystemService { 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 @@ public class GestureLauncherService extends SystemService { 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 @@ public class GestureLauncherService extends SystemService { } /** - * @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 @@ public class GestureLauncherService extends SystemService { registerContentObservers(); updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); - updatePanicButtonGestureEnabled(); + updateEmergencyGestureEnabled(); } } }; @@ -568,7 +566,7 @@ public class GestureLauncherService extends SystemService { 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 c0611374679b..c23f1cab0614 100644 --- a/services/core/java/com/android/server/SystemServerInitThreadPool.java +++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java @@ -19,6 +19,7 @@ package com.android.server; 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 @@ import java.util.concurrent.TimeUnit; * * @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 @@ public class SystemServerInitThreadPool { @GuardedBy("LOCK") private static SystemServerInitThreadPool sInstance; + private final int mSize; // used by dump() only private final ExecutorService mService; @GuardedBy("mPendingTasks") @@ -62,9 +64,9 @@ public class SystemServerInitThreadPool { 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 @@ public class SystemServerInitThreadPool { * * @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 @@ public class SystemServerInitThreadPool { 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 84d01ec3598d..6c81de6af402 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -200,21 +200,21 @@ public abstract class SystemService { /** * @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 ff2661b19b48..71a18218110e 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.Trace; 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 @@ import java.util.ArrayList; * * {@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 @@ public final class SystemServiceManager { 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: "); + if (mCurrentUser != null) { + pw.print("Current user: "); mCurrentUser.dump(pw); pw.println(); + } else { + pw.println("Current user not set!"); + } + final int targetUsersSize = mTargetUsers.size(); - for (int i = 0; i < targetUsersSize; i++) { - mTargetUsers.valueAt(i).dump(builder); - if (i != targetUsersSize - 1) builder.append(','); + 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"); } - builder.append('\n'); } - - Slog.e(TAG, builder.toString()); + 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 32d95f594ce9..112814c69d9b 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.ProviderInfoList; 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.SystemServiceManager; 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 @@ public class ActivityManagerService extends IActivityManager.Stub PackageManagerInternal mPackageManagerInt; PermissionManagerServiceInternal mPermissionManagerInt; + private TestUtilityService mTestUtilityService; /** * Whether to force background check on all apps (for battery saver) or not. @@ -5784,6 +5787,14 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } @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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * 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 31e7106979f1..e3c071fe10e2 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.ACTIVITY_TYPE_UNDEFINED; 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 android.view.Display; 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 @@ final class ActivityManagerShellCommand extends ShellCommand { return runCompat(pw); case "refresh-settings-cache": return runRefreshSettingsCache(); + case "memory-factor": + return runMemoryFactor(pw); default: return handleDefaultCommands(cmd); } @@ -3014,6 +3023,81 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 31ffb35f24b3..5e59a35fc4ee 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.app.ActivityManager.PROCESS_STATE_NONEXISTENT; 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.os.ProcessCpuTracker; 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 @@ public class AppProfiler { * 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 @@ public class AppProfiler { @GuardedBy("mService") boolean isLastMemoryLevelNormal() { - return mLastMemoryLevel <= ProcessStats.ADJ_MEM_FACTOR_NORMAL; + return mLastMemoryLevel <= ADJ_MEM_FACTOR_NORMAL; } @GuardedBy("mService") @@ -868,6 +877,11 @@ public class AppProfiler { } @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 @@ public class AppProfiler { && 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 @@ public class AppProfiler { 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 @@ public class AppProfiler { 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 @@ public class AppProfiler { 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 @@ public class AppProfiler { @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 e82a207cdd59..8f791331f0cf 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 @@ public final class LowMemDetector { 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 @@ public final class LowMemDetector { * 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 53c6758585cf..ccdd6a746239 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 @@ class ProcessRecord implements WindowProcessListener { 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 000000000000..36acc3c7344d --- /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 5dcadee20e13..9ac12ed11ded 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.RemoteException; 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 @@ public class FingerprintService extends SystemService { 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 class FingerprintService extends SystemService { 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 @@ public class FingerprintService extends SystemService { 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 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); mServiceProviders = new ArrayList<>(); - mTestSessions = new ArrayMap<>(); initializeAidlHals(); } @@ -648,7 +615,7 @@ public class FingerprintService extends SystemService { 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 c2315fdd4ccc..1ed66a247bd0 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 @@ package com.android.server.biometrics.sensors.fingerprint; 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 @@ public interface ServiceProvider { 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 000000000000..6bb40e6630bf --- /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 fec3cff6d52f..2ad1fa306781 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 @@ class FingerprintGetAuthenticatorIdClient extends ClientMonitor<ISession> { // 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 d713f981b451..4d07f583fdf5 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.IActivityTaskManager; 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.ServiceManager; 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 @@ import java.util.List; @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } + @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } }); } + + 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 d4ce896d42ec..51c30b68e0c5 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 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; 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.hardware.keymaster.HardwareAuthToken; 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.FingerprintUtils; 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 @@ import java.util.Map; */ @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,320 +100,377 @@ class Sensor implements IBinder.DeathRecipient { }); } - 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, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - mTag = tag; - mContext = context; - mHandler = handler; - mSensorProperties = sensorProperties; - mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher); - mLockoutCache = new LockoutCache(); - mAuthenticatorIds = new HashMap<>(); - mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; - } - - @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() { - return mLazySession; - } - - @NonNull FingerprintSensorPropertiesInternal getSensorProperties() { - return mSensorProperties; - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean hasSessionForUser(int userId) { - return mCurrentSession != null && mCurrentSession.mUserId == userId; - } - - 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); - }); - } + static class HalSessionCallback extends ISessionCallback.Stub { - @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; - } + /** + * Interface to sends results to the HalSessionCallback's owner. + */ + public interface Callback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } - final FingerprintRevokeChallengeClient revokeChallengeClient = - (FingerprintRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(sensorId, userId, challenge); - }); - } + @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; - @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; - } + 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; + } - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(info, vendorCode); - }); - } + @Override + public void onStateChanged(int cookie, byte state) { + // TODO(b/162973174) + } - @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; - } + @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); + }); + } - final Interruptable interruptable = (Interruptable) client; - interruptable.onError(error, vendorCode); + @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); + }); + } - if (error == Error.HW_UNAVAILABLE) { - Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); - mCurrentSession = null; - } - }); - } + @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 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; - } + @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 int currentUserId = client.getTargetUserId(); - final CharSequence name = FingerprintUtils.getInstance(sensorId) - .getUniqueName(mContext, currentUserId); - final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, sensorId); + final Interruptable interruptable = (Interruptable) client; + interruptable.onError(error, vendorCode); - final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; - enrollClient.onEnrollResult(fingerprint, remaining); - }); - } + if (error == Error.HW_UNAVAILABLE) { + mCallback.onHardwareUnavailable(); + } + }); + } - @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; - } + @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); + }); + } - 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); - } + @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); + }); + } - 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 onAuthenticationFailed() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } + @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); + }); + } - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, sensorId); - authenticationConsumer - .onAuthenticated(fp, false /* authenticated */, null /* hat */); - }); - } + @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 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; - } + @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(); + }); + } - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } + @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 onLockoutPermanent() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; + @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); + } + }); + } - 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; + @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); + } + }); + } - final FingerprintResetLockoutClient resetLockoutClient = - (FingerprintResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - }); - } + @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 onInteractionDetected() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintDetectClient)) { - Slog.e(mTag, "onInteractionDetected for non-detect client: " - + Utils.getClientName(client)); - return; - } + @Override + public void onAuthenticatorIdInvalidated() { + // TODO(159667191) + } + } - final FingerprintDetectClient fingerprintDetectClient = - (FingerprintDetectClient) client; - fingerprintDetectClient.onInteractionDetected(); - }); + 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 = () -> { + if (mTestHalEnabled) { + return new TestSession(mCurrentSession.mHalSessionCallback); + } else { + return mCurrentSession != null ? mCurrentSession.mSession : null; } + }; + } - @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; - } + @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() { + return mLazySession; + } - 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); - } - }); - } + @NonNull FingerprintSensorPropertiesInternal getSensorProperties() { + return mSensorProperties; + } - @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; - } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + boolean hasSessionForUser(int userId) { + return mCurrentSession != null && mCurrentSession.mUserId == userId; + } - 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); - } - }); - } + @Nullable Session getSessionForUser(int userId) { + if (mCurrentSession != null && mCurrentSession.mUserId == userId) { + return mCurrentSession; + } else { + return null; + } + } - @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; - } + @NonNull ITestSession createTestSession() { + return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this); + } - final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = - (FingerprintGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } + void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId) + throws RemoteException { - @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 @@ class Sensor implements IBinder.DeathRecipient { @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 000000000000..8c9a269486bb --- /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 000000000000..d5afd0c68ad9 --- /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 000000000000..e0ea99077d51 --- /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 ab4427c5235c..241c911ce9ab 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.content.pm.UserInfo; 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.R; 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } private synchronized IBiometricsFingerprint getDaemon() { + if (mTestHalEnabled) { + return new TestHal(); + } + if (mDaemon != null) { return mDaemon; } @@ -693,7 +703,27 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 000000000000..86c0875af48c --- /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 a0eafb4fc93f..b7e188c73eab 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 @@ class RadioModule { } 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 15f43a0481bd..fbd089c1f0ee 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 @@ public class DataConnectionStats extends BroadcastReceiver { 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 @@ public class DataConnectionStats extends BroadcastReceiver { : 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 @@ public class DataConnectionStats extends BroadcastReceiver { @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 7fdc7a0a524f..f99f4c65594b 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 @@ public class LingerMonitor { @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 e2187932b3e6..4c2b1e34fa78 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 class Vpn { */ 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 c686ba4c4b5c..6a4ca8da00d1 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 @@ public class SyncManager { 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 @@ public class SyncManager { 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 @@ public class SyncManager { } 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 @@ public class SyncManager { } 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(); + + 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"); + 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 @@ public class SyncManager { 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 a9e8719890ef..8980de12a31f 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 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { // 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 96679c3ab767..efe730231d36 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.Looper; 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 @@ final class HdmiCecController { 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 @@ final class HdmiCecController { 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 @@ final class HdmiCecController { 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 @@ final class HdmiCecController { } /** - * 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 @@ final class HdmiCecController { @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 @@ final class HdmiCecController { 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 @@ final class HdmiCecController { } @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 @@ final class HdmiCecController { if (address == Constants.ADDR_BROADCAST) { return true; } - return isAllocatedLocalDeviceAddress(address); + return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); } @ServiceThreadOnly @@ -682,7 +630,7 @@ final class HdmiCecController { 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 @@ final class HdmiCecController { } 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 946fb0d00d60..62a67b6243d7 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 @@ abstract class HdmiCecLocalDevice { 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 @@ abstract class HdmiCecLocalDevice { } 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 @@ abstract class HdmiCecLocalDevice { } 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 29bdd6cb40c3..fe4fd3805994 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.os.SystemProperties; 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.File; 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } // 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { boolean lastSystemAudioControlStatus = SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); - clearDeviceInfoList(); + mService.getHdmiCecNetwork().clearDeviceList(); launchDeviceDiscovery(); startQueuedActions(); } @@ -458,7 +317,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { != 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 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 0325ad929849..93cdca2d0c83 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.AudioSystem; 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.DeviceDiscoveryAction.DeviceDiscoveryCallback; 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } HdmiDeviceInfo getSafeAvrDeviceInfo() { - return 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); - } + return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } - /** - * 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - /** - * 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { }); } - 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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 28bd97e4843c..d4593afe18fe 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 @@ public class HdmiCecMessageValidator { 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 @@ public class HdmiCecMessageValidator { 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 @@ public class HdmiCecMessageValidator { 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 @@ public class HdmiCecMessageValidator { || 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 000000000000..5d75a63d0c8d --- /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 8bb89da5726f..ee86593916ae 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.os.UserHandle; 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 libcore.util.EmptyArray; 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 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { @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 @@ public class HdmiControlService extends SystemService { @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,6 +487,26 @@ public class HdmiControlService extends SystemService { @Override public void onStart() { + initService(); + publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); + + if (mCecController != null) { + // Register broadcast receiver for power state change. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SHUTDOWN); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); + + // Register ContentObserver to monitor the settings change. + registerContentObserver(); + } + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + } + + @VisibleForTesting + void initService() { if (mIoLooper == null) { mIoThread.start(); mIoLooper = mIoThread.getLooper(); @@ -521,13 +521,7 @@ public class HdmiControlService extends SystemService { 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 { + if (mCecController == null) { Slog.i(TAG, "Device does not support HDMI-CEC."); return; } @@ -537,27 +531,18 @@ public class HdmiControlService extends SystemService { 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(); - initPortInfo(); + mHdmiCecNetwork.initPortInfo(); if (mMessageValidator == null) { mMessageValidator = new HdmiCecMessageValidator(this); } - publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); - - if (mCecController != null) { - // Register broadcast receiver for power state change. - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SHUTDOWN); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); - - // Register ContentObserver to monitor the settings change. - registerContentObserver(); - } - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); } private void bootCompleted() { @@ -588,6 +573,15 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting + void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) { + mHdmiCecNetwork = hdmiCecNetwork; + } + + public HdmiCecNetwork getHdmiCecNetwork() { + return mHdmiCecNetwork; + } + + @VisibleForTesting void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) { mMhlController = hdmiMhlController; } @@ -705,7 +699,7 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { // 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 @@ public class HdmiControlService extends SystemService { 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); - } - 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; + // 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(); + } } - mCecMessageBuffer.processMessages(); - } - } - }); + }); } } @@ -888,88 +887,14 @@ public class HdmiControlService extends SystemService { 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; - } - } - - void setPortInfo(List<HdmiPortInfo> portInfo) { - synchronized (mLock) { - mPortInfo = portInfo; + return mHdmiCecNetwork.getPortInfo(); } } - /** - * 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 @@ public class HdmiControlService extends SystemService { * 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 @@ public class HdmiControlService extends SystemService { * 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 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly HdmiDeviceInfo getDeviceInfo(int logicalAddress) { assertRunOnServiceThread(); - return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress); + return mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); } @ServiceThreadOnly @@ -1092,11 +997,7 @@ public class HdmiControlService extends SystemService { * 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 @@ public class HdmiControlService extends SystemService { } return true; } - + getHdmiCecNetwork().handleCecMessage(message); if (dispatchMessageToLocalDevice(message)) { return true; } @@ -1183,7 +1084,7 @@ public class HdmiControlService extends SystemService { @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 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { List<HdmiCecLocalDevice> getAllLocalDevices() { assertRunOnServiceThread(); - return mCecController.getLocalDeviceList(); + return mHdmiCecNetwork.getLocalDeviceList(); } /** @@ -1275,8 +1181,14 @@ public class HdmiControlService extends SystemService { * * @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 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { } 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 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { 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 class HdmiControlService extends SystemService { public int getPhysicalAddress() { enforceAccessPermission(); synchronized (mLock) { - return mPhysicalAddress; + return mHdmiCecNetwork.getPhysicalAddress(); } } @@ -1934,13 +1845,8 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { @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 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { mhlDevice.sendStandby(); return; } - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { device = audioSystem(); } @@ -2262,7 +2156,7 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { 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 class HdmiControlService extends SystemService { } public HdmiCecLocalDeviceTv tv() { - return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); + return (HdmiCecLocalDeviceTv) mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); } boolean isTvDevice() { @@ -2857,11 +2750,11 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { } private boolean canGoToStandby() { - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { if (!device.canGoToStandby()) return false; } return true; @@ -3025,7 +2918,7 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { return; } mCecController.clearLogicalAddress(); - mCecController.clearLocalDevices(); + mHdmiCecNetwork.clearLogicalAddress(); + mHdmiCecNetwork.clearLocalDevices(); } @ServiceThreadOnly @@ -3051,7 +2945,7 @@ public class HdmiControlService extends SystemService { 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 @@ public class HdmiControlService extends SystemService { // 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 7670dccf9c0a..ece78bfa2769 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 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { } 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 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { 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 6753368911b9..edc7bd95a017 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.hardware.hdmi.HdmiDeviceInfo; import android.util.Slog; import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; + import java.io.UnsupportedEncodingException; /** @@ -164,7 +165,8 @@ final class NewDeviceAction extends HdmiCecFeatureAction { 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 @@ final class NewDeviceAction extends HdmiCecFeatureAction { 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 e78a86c21453..53f9a109d08d 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.HdmiControlManager; 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 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { 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 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { @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 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { 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 a62d0b63221c..909fcda26c39 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 static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN; 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 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { } 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 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { } 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 6c8694ea74ad..6c147ed5e6d6 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 @@ final class RoutingControlAction extends HdmiCecFeatureAction { } 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 cdb73d8ad6a2..9ca4d35f4579 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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 179fb7d2f723..b4a172393ba6 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.SparseBooleanArray; 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 @@ import java.util.Objects; class LocationProviderManager extends ListenerMultiplexer<Object, LocationProviderManager.LocationTransport, - LocationProviderManager.LocationListenerOperation, LocationProviderManager.Registration, ProviderRequest> implements AbstractLocationProvider.Listener { @@ -225,16 +223,8 @@ class LocationProviderManager extends } } - 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends } @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 @@ class LocationProviderManager extends } @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 @@ class LocationProviderManager extends @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 @@ class LocationProviderManager extends getRequest().isLocationSettingsIgnored(), maxLocationAgeMs); if (lastLocation != null) { - return acceptLocationChange(lastLocation); + executeOperation(acceptLocationChange(lastLocation)); } } } - - return null; } @Override @@ -703,9 +679,9 @@ class LocationProviderManager extends } @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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends } 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 @@ class LocationProviderManager extends } @Override - public void onOperationFailure(LocationListenerOperation operation, Exception exception) { + public void onOperationFailure(ListenerOperation<LocationTransport> operation, + Exception exception) { onTransportFailure(exception); } @@ -913,7 +894,8 @@ class LocationProviderManager extends } @Override - public void onOperationFailure(LocationListenerOperation operation, Exception exception) { + public void onOperationFailure(ListenerOperation<LocationTransport> operation, + Exception exception) { onTransportFailure(exception); } @@ -988,28 +970,24 @@ class LocationProviderManager extends @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 @@ class LocationProviderManager extends } @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 @@ class LocationProviderManager extends 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 - 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); - } - } + // we currently don't hold a wakelock for getCurrentLocation deliveries + listener.deliverOnLocationChanged(deliveryLocation, null); + mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity()); - synchronized (mLock) { - remove(); - } + synchronized (mLock) { + remove(); } }; } @@ -1114,7 +1077,7 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends } } - @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 @@ class LocationProviderManager extends * 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 @@ class LocationProviderManager extends } } - @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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends synchronized (mLock) { final long ident = Binder.clearCallingIdentity(); try { - addRegistration(listener.asBinder(), registration); + putRegistration(listener.asBinder(), registration); } finally { Binder.restoreCallingIdentity(ident); } @@ -1578,7 +1537,7 @@ class LocationProviderManager extends synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { - addRegistration(pendingIntent, registration); + putRegistration(pendingIntent, registration); } finally { Binder.restoreCallingIdentity(identity); } @@ -1673,7 +1632,7 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 @@ class LocationProviderManager extends 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 c91ee824ff61..7a59cba02dd9 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.stats.location.LocationStatsEnums; 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 @@ import java.util.Objects; * 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 @@ public class GeofenceManager extends } @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 @@ public class GeofenceManager extends 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 ec48d4cbcecd..7592d22a3a78 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.IInterface; 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 @@ import java.util.Objects; */ 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 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter 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 74284f357f61..4a3f94f9b73a 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 @@ public final class GnssMeasurementsProvider extends @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 bc675ceda970..d6b179bab5a2 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.IBinder; 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 @@ import com.android.internal.listeners.ListenerExecutor.ListenerOperation; * @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 0318ffb519ba..6b936169c82f 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 @@ import java.util.function.Predicate; * * @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 @@ public abstract class ListenerMultiplexer<TKey, TListener, 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 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 addRegistration(@NonNull TKey key, @NonNull TRegistration registration) { + 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, // 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, * 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, * 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, * 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 @@ public abstract class ListenerMultiplexer<TKey, TListener, onRegistrationActiveChanged(registration); } - private void execute(TRegistration registration, TListenerOperation operation) { - registration.executeInternal(operation); - } - /** * Dumps debug information. */ @@ -606,7 +615,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, @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 @@ public abstract class ListenerMultiplexer<TKey, TListener, } @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 @@ public abstract class ListenerMultiplexer<TKey, TListener, @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 @@ public abstract class ListenerMultiplexer<TKey, TListener, 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 d7ecbcb7cfdf..fa21b3a8e369 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 @@ import java.util.concurrent.Executor; * 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 @@ public class ListenerRegistration<TListener, * 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 @@ public class ListenerRegistration<TListener, 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 e57b5322de8d..0aafb2929d56 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.app.PendingIntent; 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 @@ import com.android.internal.listeners.ListenerExecutor.ListenerOperation; * @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 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext 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 242bf323f6cd..4eca577dcf4f 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.location.util.identity.CallerIdentity; 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 @@ import java.util.concurrent.Executor; * * @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 d3b5f6696167..618ff24b873b 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 @@ package com.android.server.location.listeners; import android.annotation.Nullable; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; - import java.util.Objects; import java.util.concurrent.Executor; @@ -29,11 +27,9 @@ import java.util.concurrent.Executor; * * @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 @@ public abstract class RemovableListenerRegistration<TRequest, TListener, * 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 d97abae59dd3..0c2fc9142d92 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 @@ import java.util.concurrent.Executor; * * @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 26ace47776be..72803ac35a3f 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 @@ public final class IncrementalStates { } } + /** + * 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 8eebf2a8d9d9..b679c0fbab83 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.IApplicationThread; 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 @@ public class LauncherAppsService extends SystemService { } @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 @@ public class LauncherAppsService extends SystemService { 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 @@ public class LauncherAppsService extends SystemService { } } 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 410fbe57bf26..7eb4fd968c36 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.SharedLibraryInfo; 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.SomeArgs; 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 @@ import java.util.function.Supplier; * </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 @@ public class PackageManagerService extends IPackageManager.Stub boolean mPromoteSystemApps; private final PackageManagerInternal mPmInternal; + private final TestUtilityService mTestUtilityService; @GuardedBy("mLock") @@ -1192,6 +1195,7 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub } } - 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub } 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 @@ public class PackageManagerService extends IPackageManager.Stub } @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 be7c7c6ff1d6..ac76cf71ef67 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 @@ public abstract class PackageSettingBase extends SettingBase { } /** + * 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 6ffc5983417a..155d71673e06 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 @@ public final class BasePermission { 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 @@ public final class BasePermission { return mPermissionInfo.packageName; } + public boolean isPermissionDefinitionChanged() { + return mPermissionDefinitionChanged; + } + public int getType() { return mType; } @@ -148,6 +154,10 @@ public final class BasePermission { mReconciled = permissionInfo != null; } + public void setPermissionDefinitionChanged(boolean shouldOverride) { + mPermissionDefinitionChanged = shouldOverride; + } + public boolean hasGids() { return mGids.length != 0; } @@ -364,6 +374,7 @@ public final class BasePermission { @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 @@ public final class BasePermission { 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 @@ public final class BasePermission { 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 @@ public final class BasePermission { 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 4d43969578cd..da4ef63d6945 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage, oldPackage, allPackageNames, mDefaultPermissionCallback); } + + @Override + public void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames) { + PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged( + permissionsToRevoke, allPackageNames, mDefaultPermissionCallback); + } + @Override - public void addAllPermissions(AndroidPackage pkg, boolean chatty) { - PermissionManagerService.this.addAllPermissions(pkg, chatty); + 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 5ea3458fcbfa..20e9c5dcb521 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 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager @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 9d08b1be8d36..f50ce1ab8e8d 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 @@ class ActivityMetricsLogger { */ 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 @@ class ActivityMetricsLogger { 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 d6a1f4a812df..0a7f08bfbe8c 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mLetterbox != null) { mLetterbox.hide(); } + task.maybeUpdateLetterboxBounds(this, getLetterboxParams(w)); } void updateLetterboxSurface(WindowState winHint) { @@ -1377,6 +1376,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A stack.mUndrawnActivitiesBelowTopTranslucent.add(this); } setVisibility(true); - mSetToSleep = false; app.postPendingUiCleanMsg(true); if (reportToClient) { mClientVisibilityDeferred = false; @@ -5118,9 +5121,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 a8079cfa1c85..a068d2b7c823 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } 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 c8a8f81ebca1..25b2523b1a3e 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 @@ class ActivityStarter { } } - 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { || 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 @@ class ActivityStarter { || 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 @@ class ActivityStarter { // 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { 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 8e5add916620..6749cdf6fe7d 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_SPLIT_SCREEN_PRIMAR 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.WindowConfiguration; 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 7c5c43568889..7641de5ab7b9 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || 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 72ecf6be430d..2ea4b57d9de3 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 @@ class DragState { * 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 @@ class DragState { // 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 @@ class DragState { // 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 @@ class DragState { 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 c1e518b8b82c..04b303053d8f 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_L 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 72edb86283d2..6fbd35164874 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.DEVICE_POWER; 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.Process; 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { */ @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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } 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 6804684a1f37..ef1a3be70f9d 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 @@ class Task extends WindowContainer<WindowContainer> { // {@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 @@ class Task extends WindowContainer<WindowContainer> { */ 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 @@ class Task extends WindowContainer<WindowContainer> { @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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { @Override public boolean onDescendantOrientationChanged(IBinder freezeDisplayToken, - ConfigurationContainer requestingContainer) { + WindowContainer requestingContainer) { if (super.onDescendantOrientationChanged(freezeDisplayToken, requestingContainer)) { return true; } @@ -3291,6 +3329,18 @@ class Task extends WindowContainer<WindowContainer> { // 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 @@ class Task extends WindowContainer<WindowContainer> { } 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { ? 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { return false; } + if (mDeferTaskAppear) { + return false; + } + if (mCreatedByOrganizer) { return true; } @@ -5252,14 +5337,12 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { } 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 @@ class Task extends WindowContainer<WindowContainer> { } 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { // 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { // 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { 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 @@ class Task extends WindowContainer<WindowContainer> { 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 9392666fbf54..e7213192dfd3 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.app.WindowConfiguration; 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 @@ final class TaskDisplayArea extends DisplayArea<Task> { 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 @@ final class TaskDisplayArea extends DisplayArea<Task> { 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 @@ final class TaskDisplayArea extends DisplayArea<Task> { * @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 class TaskDisplayArea extends DisplayArea<Task> { 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 @@ final class TaskDisplayArea extends DisplayArea<Task> { } @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 class TaskDisplayArea extends DisplayArea<Task> { } 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 @@ final class TaskDisplayArea extends DisplayArea<Task> { 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 6504f00905e7..a70efbcf5500 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.HashMap; 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 1e938a413212..0f6b62bae55f 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** * 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * 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 045bc4e15e74..0eadbf296323 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.IntentFilter; 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 @@ public class WindowManagerService extends IWindowManager.Stub final AppOpsManager mAppOps; final PackageManagerInternal mPmInternal; + private final TestUtilityService mTestUtilityService; final DisplayWindowSettings mDisplayWindowSettings; @@ -1265,6 +1267,7 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } @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 2e7905c64049..2d69dcbcfbd0 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 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } - 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()) { + 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; + } if (DEBUG_ACTIVITY_STARTS) { - Slog.d(TAG, "[WindowProcessController(" + mPid - + ")] Activity start allowed: within " - + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period"); + Slog.d(TAG, "[WindowProcessController(" + mPid + ")] Activity start within " + + ACTIVITY_BG_START_GRACE_PERIOD_MS + + "ms grace period but also within stop app switch window"); } - 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"); - } + } } // allow if the proc is instrumenting with background activity starts privs if (mInstrumentingWithBackgroundActivityStartPrivileges) { @@ -539,7 +543,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio 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 d39c7a6fd8df..5d78f127f77f 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 @@ using IPowerV1_0 = android::hardware::power::V1_0::IPower; 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 @@ struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient { 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 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) // 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 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) 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 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) *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 ddda392ea951..411d8d60b7c4 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .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 975e2265b60c..e116a353c723 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -76,14 +76,18 @@ import android.provider.Settings; 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 dalvik.system.VMRuntime; 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 @@ public final class SystemServer { private Future<?> mZygotePreload; private Future<?> mBlobStoreServiceStart; + private final SystemServerDumper mDumper = new SystemServerDumper(); + + /** * The pending WTF to be logged into dropbox. */ @@ -446,6 +459,75 @@ public final class SystemServer { 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 @@ public final class SystemServer { // 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 @@ public final class SystemServer { 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 18bd6b17f340..b98021bc2cab 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.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; +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.junit.Assert.fail; 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.Handler; 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.JobSchedulerService.Constants; 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.Before; 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 @@ public class QuotaControllerTest { private QuotaController.QcConstants mQcConstants; private int mSourceUid; private IUidObserver mUidObserver; + DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private MockitoSession mMockingSession; @Mock @@ -133,6 +142,7 @@ public class QuotaControllerTest { mMockingSession = mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) + .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) .startMocking(); @@ -164,6 +174,18 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { // 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 class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 class QuotaControllerTest { 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 @@ public class QuotaControllerTest { @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 @@ public class QuotaControllerTest { @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 @@ public class QuotaControllerTest { @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 @@ public class QuotaControllerTest { @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 @@ public class QuotaControllerTest { @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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 29d3f29ad7e1..d7fef604d25b 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 android.platform.test.annotations.Presubmit; 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 @@ public class ListenerMultiplexerTest { void onRegistrationAdded(Consumer<TestListenerRegistration> consumer, TestListenerRegistration registration); + void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer, + TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration); + void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer, TestListenerRegistration registration); @@ -90,8 +92,39 @@ public class ListenerMultiplexerTest { 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 @@ -319,8 +352,7 @@ public class ListenerMultiplexerTest { } private static class TestListenerRegistration extends - RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>, - ListenerOperation<Consumer<TestListenerRegistration>>> { + RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> { boolean mActive = true; @@ -332,9 +364,7 @@ public class ListenerMultiplexerTest { 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 class ListenerMultiplexerTest { } 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 @@ public class ListenerMultiplexerTest { } @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 d2d85c860cd5..e76c5a476c48 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 androidx.test.runner.AndroidJUnit4; 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 @@ public class GestureLauncherServiceTest { } @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 @@ public class GestureLauncherServiceTest { } @Test - public void testHandlePanicGesture_userSetupComplete() { + public void testHandleEmergencyGesture_userSetupComplete() { withUserSetupCompleteValue(true); - assertTrue(mGestureLauncherService.handlePanicButtonGesture()); + assertTrue(mGestureLauncherService.handleEmergencyGesture()); } @Test @@ -196,10 +195,10 @@ public class GestureLauncherServiceTest { } @Test - public void testHandlePanicGesture_userSetupNotComplete() { + public void testHandleEmergencyGesture_userSetupNotComplete() { withUserSetupCompleteValue(false); - assertFalse(mGestureLauncherService.handlePanicButtonGesture()); + assertFalse(mGestureLauncherService.handleEmergencyGesture()); } @Test @@ -223,9 +222,9 @@ public class GestureLauncherServiceTest { } @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 @@ public class GestureLauncherServiceTest { @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 @@ public class GestureLauncherServiceTest { 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 @@ public class GestureLauncherServiceTest { 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 @@ public class GestureLauncherServiceTest { @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 @@ public class GestureLauncherServiceTest { 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 @@ public class GestureLauncherServiceTest { @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 @@ public class GestureLauncherServiceTest { 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 @@ public class GestureLauncherServiceTest { 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 241b5a9523b1..b360ae88b1ba 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.appop.AppOpsService; 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 @@ public class ActivityManagerServiceTest { 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 b2d7177e04eb..4f58c87e61ea 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 android.content.pm.PackageManagerInternal; 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 @@ public class OomAdjusterTests { 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 @@ public class OomAdjusterTests { 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 c4f7b9547277..30b1b3e78ad3 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 2a9c3942211c..8af7332e24ed 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 @@ public class ActiveSourceActionTest { 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 1385376b740d..37a75e3822aa 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 @@ public class ArcInitiationActionFromAvrTest { 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 169f885a7253..6027c3e4eeab 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 @@ public class ArcTerminationActionFromAvrTest { 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 2c42791fabce..bb57a69d6f51 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 com.google.common.collect.Iterables; 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 @@ final class FakeNativeWrapper implements NativeWrapper { }; 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 @@ final class FakeNativeWrapper implements NativeWrapper { @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 74fd6830de61..433f6e7938e2 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class HdmiCecLocalDeviceAudioSystemTest { 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 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @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 @@ public class HdmiCecLocalDeviceAudioSystemTest { 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 ef98b98ba7e1..440befcb3368 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class HdmiCecLocalDevicePlaybackTest { 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 class HdmiCecLocalDevicePlaybackTest { 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 @@ public class HdmiCecLocalDevicePlaybackTest { 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 ce1cdf369076..bf4851b927b1 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class HdmiCecLocalDeviceTvTest { 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 @@ public class HdmiCecLocalDeviceTvTest { @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 5a05fc6eda4c..debf20bffe28 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 @@ public class HdmiCecMessageValidatorTest { 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 000000000000..080b52bbbc6a --- /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 c4068d34c00d..74a00521d7f4 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 com.google.common.truth.Truth.assertThat; 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 @@ import java.util.ArrayList; @RunWith(JUnit4.class) public class HdmiControlServiceBinderAPITest { + private Context mContext; + private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice { private boolean mCanGoToStandby; @@ -111,8 +115,12 @@ public class HdmiControlServiceBinderAPITest { @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 @@ public class HdmiControlServiceBinderAPITest { 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 @@ public class HdmiControlServiceBinderAPITest { 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 @@ public class HdmiControlServiceBinderAPITest { @Test public void oneTouchPlay_addressAllocated() { + setHdmiControlEnabled(true); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); @@ -204,4 +215,10 @@ public class HdmiControlServiceBinderAPITest { 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 2f48b5ee4c70..8f631a307f15 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 @@ public class HdmiControlServiceTest { 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 @@ public class HdmiControlServiceTest { } @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 6be28d9a13be..0e4bfab0d94c 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 @@ public class SystemAudioInitiationActionFromAvrTest { break; case Constants.MESSAGE_INITIATE_ARC: break; - default: - throw new IllegalArgumentException("Unexpected message"); } } @@ -159,6 +157,14 @@ public class SystemAudioInitiationActionFromAvrTest { 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 @@ public class SystemAudioInitiationActionFromAvrTest { } }; 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 72afca0300cd..9b25d0dc9c77 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 androidx.test.runner.AndroidJUnit4; 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 @@ public class ApexManagerTest { 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 86758f18a407..40d959d9793d 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 @@ public class IncrementalStatesTest { 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 9be31647cf73..f1d3e1840588 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 static org.mockito.Mockito.timeout; 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 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @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 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { 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 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { 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 caf8a720e26c..b6323313dd27 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 @@ public class ActivityStackTests extends WindowTestsBase { .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 e47881917b2c..3720e520b1b9 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 @@ public class ActivityStarterTests extends WindowTestsBase { 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 64a05bb361e9..747fb8999276 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 @@ package com.android.server.wm; 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 @@ import java.util.concurrent.TimeUnit; @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 @@ public class DragDropControllerTests extends WindowTestsBase { 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 dffa7904bb44..d68dde5734ca 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.doReturn; 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.assertEquals; 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 @@ public class SizeCompatTests extends WindowTestsBase { 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 @@ public class SizeCompatTests extends WindowTestsBase { 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 ace0400bc293..3203ccb400d6 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.times; 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 @@ public class TaskTests extends WindowTestsBase { } @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()); - - doReturn(true).when(leafTask1).showToCurrentUser(); - rootTask.switchUser(10); - assertEquals(1, rootTask.getChildCount()); - assertEquals(leafTask1, 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); + + // 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 7a1f65a3b62c..b4480aea3ce4 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { } }; 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 @@ public class WindowOrganizerTests extends WindowTestsBase { } }; 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 @@ public class WindowOrganizerTests extends WindowTestsBase { } }; 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 1eb7cbed02c8..62f04a1c830a 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 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 @@ class WindowTestsBase extends SystemServiceTestsBase { // 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 82da4475c1b9..ae485d502a1e 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -32,6 +32,7 @@ import android.content.Intent; 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 @@ public class TelecomManager { } /** + * 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 @@ public class TelecomManager { } } + 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 7c6f1df972f3..e636b93deace 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -179,6 +179,11 @@ interface ITelecomService { 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 63e0e5a36a26..a58b6d84cbd1 100644 --- a/telephony/api/system-current.txt +++ b/telephony/api/system-current.txt @@ -747,6 +747,7 @@ package android.telephony { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 @@ package android.telephony { 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 92238420fd32..f8a200a5f8d3 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -369,7 +369,6 @@ public final class NetworkRegistrationInfo implements Parcelable { * 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 20eeb1da0d99..aa68dcf5a88e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13672,6 +13672,149 @@ public class TelephonyManager { } } + /** + * 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 0d8351d1ff12..d33835f27053 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2216,4 +2216,20 @@ interface ITelephony { * 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 d524299d7ede..953a2924dc44 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -494,6 +494,8 @@ public interface RILConstants { 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 992493143179..6cc2e2236127 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 @@ class CloseImeAutoOpenWindowToHomeTest( 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 46f584b51e8c..8d9881ec2063 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 @@ class CloseImeWindowToHomeTest( 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 1194933ee315..ad23d9f48568 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 @@ class OpenAppColdTest( 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 136be29e421d..5886a61eed34 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 @@ class OpenAppWarmTest( 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 4195df72864c..20f564e80b3d 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 e1693129892f..11a83ebf6dd7 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_RESTRICTED; 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 @@ public class NetworkCapabilitiesTest { } @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 da644021e30e..28ff606d0381 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 @@ ALL_FLAGS = FLAGS_API_LIST + [ 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 @@ HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersectio 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 @@ def get_args(): 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 @@ class FlagsDict: 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 @@ class FlagsDict: 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 @@ class FlagsDict: # 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 @@ def main(argv): 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)) |