diff options
291 files changed, 8074 insertions, 2244 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 44afbe6aff51..14cce19ef676 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -16,6 +16,11 @@ package com.android.server.job.controllers; +import static android.app.job.JobInfo.PRIORITY_DEFAULT; +import static android.app.job.JobInfo.PRIORITY_HIGH; +import static android.app.job.JobInfo.PRIORITY_LOW; +import static android.app.job.JobInfo.PRIORITY_MAX; +import static android.app.job.JobInfo.PRIORITY_MIN; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; @@ -43,9 +48,12 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; +import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseArrayMap; +import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; @@ -91,6 +99,23 @@ public final class FlexibilityController extends StateController { */ private long mFallbackFlexibilityDeadlineMs = FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; + /** + * The default deadline that all flexible constraints should be dropped by if a job lacks + * a deadline, keyed by job priority. + */ + private SparseLongArray mFallbackFlexibilityDeadlines = + FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES; + /** + * The scores to use for each job, keyed by job priority. + */ + private SparseIntArray mFallbackFlexibilityDeadlineScores = + FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES; + /** + * The amount of time to add (scaled by job run score) to the fallback flexibility deadline, + * keyed by job priority. + */ + private SparseLongArray mFallbackFlexibilityAdditionalScoreTimeFactors = + FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS; private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; @@ -117,10 +142,10 @@ public final class FlexibilityController extends StateController { /** * The percent of a job's lifecycle to drop number of required constraints. - * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle, - * the controller should have i+1 constraints dropped. + * mPercentsToDropConstraints[i] denotes that at x% of a Jobs lifecycle, + * the controller should have i+1 constraints dropped. Keyed by job priority. */ - private int[] mPercentToDropConstraints; + private SparseArray<int[]> mPercentsToDropConstraints; /** * Keeps track of what flexible constraints are satisfied at the moment. @@ -199,6 +224,86 @@ public final class FlexibilityController extends StateController { } }; + /** Helper object to track job run score for each app. */ + private static class JobScoreTracker { + private static class JobScoreBucket { + @ElapsedRealtimeLong + public long startTimeElapsed; + public int score; + + private void reset() { + startTimeElapsed = 0; + score = 0; + } + } + + private static final int NUM_SCORE_BUCKETS = 24; + private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS; + private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS]; + private int mScoreBucketIndex = 0; + + public void addScore(int add, long nowElapsed) { + JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex]; + if (bucket == null) { + bucket = new JobScoreBucket(); + bucket.startTimeElapsed = nowElapsed; + mScoreBuckets[mScoreBucketIndex] = bucket; + } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) { + // The bucket is too old. + bucket.reset(); + bucket.startTimeElapsed = nowElapsed; + } else if (bucket.startTimeElapsed + < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) { + // The current bucket's duration has completed. Move on to the next bucket. + mScoreBucketIndex = (mScoreBucketIndex + 1) % NUM_SCORE_BUCKETS; + addScore(add, nowElapsed); + return; + } + + bucket.score += add; + } + + public int getScore(long nowElapsed) { + int score = 0; + final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS; + for (JobScoreBucket bucket : mScoreBuckets) { + if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) { + score += bucket.score; + } + } + return score; + } + + public void dump(@NonNull IndentingPrintWriter pw, long nowElapsed) { + pw.print("{"); + + boolean printed = false; + for (int x = 0; x < mScoreBuckets.length; ++x) { + final int idx = (mScoreBucketIndex + 1 + x) % mScoreBuckets.length; + final JobScoreBucket jsb = mScoreBuckets[idx]; + if (jsb == null || jsb.startTimeElapsed == 0) { + continue; + } + if (printed) { + pw.print(", "); + } + TimeUtils.formatDuration(jsb.startTimeElapsed, nowElapsed, pw); + pw.print("="); + pw.print(jsb.score); + printed = true; + } + + pw.print("}"); + } + } + + /** + * Set of {@link JobScoreTracker JobScoreTrackers} for each app. + * Keyed by source UID -> source package. + **/ + private final SparseArrayMap<String, JobScoreTracker> mJobScoreTrackers = + new SparseArrayMap<>(); + private static final int MSG_CHECK_ALL_JOBS = 0; /** Check the jobs in {@link #mJobsToCheck} */ private static final int MSG_CHECK_JOBS = 1; @@ -228,8 +333,8 @@ public final class FlexibilityController extends StateController { mFcConfig = new FcConfig(); mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( mContext, AppSchedulingModuleThread.get().getLooper()); - mPercentToDropConstraints = - mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; + mPercentsToDropConstraints = + FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; mPrefetchController = prefetchController; if (mFlexibilityEnabled) { @@ -272,6 +377,36 @@ public final class FlexibilityController extends StateController { } @Override + public void prepareForExecutionLocked(JobStatus jobStatus) { + // Use the job's requested priority to determine its score since that is what the developer + // selected and it will be stable across job runs. + final int score = mFallbackFlexibilityDeadlineScores + .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100); + JobScoreTracker jobScoreTracker = + mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName()); + if (jobScoreTracker == null) { + jobScoreTracker = new JobScoreTracker(); + mJobScoreTrackers.add(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), + jobScoreTracker); + } + jobScoreTracker.addScore(score, sElapsedRealtimeClock.millis()); + } + + @Override + public void unprepareFromExecutionLocked(JobStatus jobStatus) { + // The job didn't actually start. Undo the score increase. + JobScoreTracker jobScoreTracker = + mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName()); + if (jobScoreTracker == null) { + Slog.e(TAG, "Unprepared a job that didn't result in a score change"); + return; + } + final int score = mFallbackFlexibilityDeadlineScores + .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100); + jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis()); + } + + @Override @GuardedBy("mLock") public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) { if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) { @@ -286,12 +421,33 @@ public final class FlexibilityController extends StateController { public void onAppRemovedLocked(String packageName, int uid) { final int userId = UserHandle.getUserId(uid); mPrefetchLifeCycleStart.delete(userId, packageName); + mJobScoreTrackers.delete(uid, packageName); + for (int i = mJobsToCheck.size() - 1; i >= 0; --i) { + final JobStatus js = mJobsToCheck.valueAt(i); + if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName)) + || (js.getUid() == uid && js.getCallingPackageName().equals(packageName))) { + mJobsToCheck.removeAt(i); + } + } } @Override @GuardedBy("mLock") public void onUserRemovedLocked(int userId) { mPrefetchLifeCycleStart.delete(userId); + for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) { + final int uid = mJobScoreTrackers.keyAt(u); + if (UserHandle.getUserId(uid) == userId) { + mJobScoreTrackers.deleteAt(u); + } + } + for (int i = mJobsToCheck.size() - 1; i >= 0; --i) { + final JobStatus js = mJobsToCheck.valueAt(i); + if (UserHandle.getUserId(js.getSourceUid()) == userId + || UserHandle.getUserId(js.getUid()) == userId) { + mJobsToCheck.removeAt(i); + } + } } boolean isEnabled() { @@ -308,9 +464,9 @@ public final class FlexibilityController extends StateController { || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP // Only exclude DEFAULT+ priority jobs for BFGS+ apps || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE - && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) + && js.getEffectivePriority() >= PRIORITY_DEFAULT) // For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs. - || (js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT + || (js.getEffectivePriority() >= PRIORITY_DEFAULT && mPowerAllowlistedApps.contains(js.getSourcePackageName())) || hasEnoughSatisfiedConstraintsLocked(js) || mService.isCurrentlyRunningLocked(js); @@ -462,7 +618,14 @@ public final class FlexibilityController extends StateController { @VisibleForTesting @GuardedBy("mLock") - long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) { + int getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed) { + final JobScoreTracker scoreTracker = mJobScoreTrackers.get(uid, pkgName); + return scoreTracker == null ? 0 : scoreTracker.getScore(nowElapsed); + } + + @VisibleForTesting + @GuardedBy("mLock") + long getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest) { if (js.getJob().isPrefetch()) { final long estimatedLaunchTime = mPrefetchController.getNextEstimatedLaunchTimeLocked(js); @@ -486,15 +649,28 @@ public final class FlexibilityController extends StateController { (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2), mMaxRescheduledDeadline); } - return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME - ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed(); + if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) { + // Intentionally use the effective priority here. If a job's priority was effectively + // lowered, it will be less likely to run quickly given other policies in JobScheduler. + // Thus, there's no need to further delay the job based on flex policy. + final int jobPriority = js.getEffectivePriority(); + final int jobScore = + getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed); + // Set an upper limit on the fallback deadline so that the delay doesn't become extreme. + final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs, + mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs) + + mFallbackFlexibilityAdditionalScoreTimeFactors + .get(jobPriority, MINUTE_IN_MILLIS) * jobScore); + return earliest + fallbackDeadlineMs; + } + return js.getLatestRunTimeElapsed(); } @VisibleForTesting @GuardedBy("mLock") int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) { final long earliest = getLifeCycleBeginningElapsedLocked(js); - final long latest = getLifeCycleEndElapsedLocked(js, earliest); + final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest); if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) { return 0; } @@ -510,7 +686,8 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") long getNextConstraintDropTimeElapsedLocked(JobStatus js) { final long earliest = getLifeCycleBeginningElapsedLocked(js); - final long latest = getLifeCycleEndElapsedLocked(js, earliest); + final long latest = + getLifeCycleEndElapsedLocked(js, sElapsedRealtimeClock.millis(), earliest); return getNextConstraintDropTimeElapsedLocked(js, earliest, latest); } @@ -518,15 +695,27 @@ public final class FlexibilityController extends StateController { @ElapsedRealtimeLong @GuardedBy("mLock") long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) { + final int[] percentsToDropConstraints = + getPercentsToDropConstraints(js.getEffectivePriority()); if (latest == NO_LIFECYCLE_END - || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) { + || js.getNumDroppedFlexibleConstraints() == percentsToDropConstraints.length) { return NO_LIFECYCLE_END; } - final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()]; + final int percent = percentsToDropConstraints[js.getNumDroppedFlexibleConstraints()]; final long percentInTime = ((latest - earliest) * percent) / 100; return earliest + percentInTime; } + @NonNull + private int[] getPercentsToDropConstraints(int priority) { + int[] percentsToDropConstraints = mPercentsToDropConstraints.get(priority); + if (percentsToDropConstraints == null) { + Slog.wtf(TAG, "No %-to-drop for priority " + JobInfo.getPriorityString(priority)); + return new int[]{50, 60, 70, 80}; + } + return percentsToDropConstraints; + } + @Override @GuardedBy("mLock") public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { @@ -681,10 +870,12 @@ public final class FlexibilityController extends StateController { Integer.bitCount(getRelevantAppliedConstraintsLocked(js)); js.setNumAppliedFlexibleConstraints(numAppliedConstraints); + final int[] percentsToDropConstraints = + getPercentsToDropConstraints(js.getEffectivePriority()); final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); int toDrop = 0; for (int i = 0; i < numAppliedConstraints; i++) { - if (curPercent >= mPercentToDropConstraints[i]) { + if (curPercent >= percentsToDropConstraints[i]) { toDrop++; } } @@ -705,8 +896,10 @@ public final class FlexibilityController extends StateController { final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); int toDrop = 0; final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints(); + final int[] percentsToDropConstraints = + getPercentsToDropConstraints(js.getEffectivePriority()); for (int i = 0; i < jsMaxFlexibleConstraints; i++) { - if (curPercent >= mPercentToDropConstraints[i]) { + if (curPercent >= percentsToDropConstraints[i]) { toDrop++; } } @@ -733,7 +926,7 @@ public final class FlexibilityController extends StateController { return mTrackedJobs.size(); } - public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { + public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed) { for (int i = 0; i < mTrackedJobs.size(); i++) { ArraySet<JobStatus> jobs = mTrackedJobs.get(i); for (int j = 0; j < jobs.size(); j++) { @@ -744,8 +937,18 @@ public final class FlexibilityController extends StateController { js.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, js.getSourceUid()); - pw.print(" Num Required Constraints: "); + pw.print("-> Num Required Constraints: "); pw.print(js.getNumRequiredFlexibleConstraints()); + + pw.print(", lifecycle=["); + final long earliest = getLifeCycleBeginningElapsedLocked(js); + pw.print(earliest); + pw.print(", ("); + pw.print(getCurPercentOfLifecycleLocked(js, nowElapsed)); + pw.print("%), "); + pw.print(getLifeCycleEndElapsedLocked(js, nowElapsed, earliest)); + pw.print("]"); + pw.println(); } } @@ -768,7 +971,7 @@ public final class FlexibilityController extends StateController { public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) { synchronized (mLock) { final long earliest = getLifeCycleBeginningElapsedLocked(js); - final long latest = getLifeCycleEndElapsedLocked(js, earliest); + final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest); final long nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js, earliest, latest); @@ -936,10 +1139,16 @@ public final class FlexibilityController extends StateController { FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms"; static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE = FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms"; + static final String KEY_FALLBACK_FLEXIBILITY_DEADLINES = + FC_CONFIG_PREFIX + "fallback_flexibility_deadlines"; + static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES = + FC_CONFIG_PREFIX + "fallback_flexibility_deadline_scores"; + static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS = + FC_CONFIG_PREFIX + "fallback_flexibility_deadline_additional_score_time_factors"; static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms"; - static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = - FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints"; + static final String KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = + FC_CONFIG_PREFIX + "percents_to_drop_flexible_constraints"; static final String KEY_MAX_RESCHEDULED_DEADLINE_MS = FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms"; static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = @@ -952,9 +1161,50 @@ public final class FlexibilityController extends StateController { static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS; - private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; + static final SparseLongArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray(); + static final SparseIntArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES = + new SparseIntArray(); + static final SparseLongArray + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS = + new SparseLongArray(); @VisibleForTesting - final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; + static final SparseArray<int[]> DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = + new SparseArray<>(); + + static { + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .put(PRIORITY_MAX, 0); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS); + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS); + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .put(PRIORITY_MAX, new int[]{1, 2, 3, 4}); + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .put(PRIORITY_HIGH, new int[]{33, 50, 60, 75}); + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80}); + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .put(PRIORITY_LOW, new int[]{50, 60, 70, 80}); + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .put(PRIORITY_MIN, new int[]{55, 65, 75, 85}); + } + + private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS; private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS; @VisibleForTesting @@ -968,9 +1218,11 @@ public final class FlexibilityController extends StateController { public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; - /** The percentages of a jobs' lifecycle to drop the number of required constraints. */ - public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = - DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; + /** + * The percentages of a jobs' lifecycle to drop the number of required constraints. + * Keyed by job priority. + */ + public SparseArray<int[]> PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = new SparseArray<>(); /** Initial fallback flexible deadline for rescheduled jobs. */ public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; /** The max deadline for rescheduled jobs. */ @@ -980,10 +1232,56 @@ public final class FlexibilityController extends StateController { * it in order to run jobs. */ public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; + /** + * The base fallback deadlines to use if a job doesn't have its own deadline. Values are in + * milliseconds and keyed by job priority. + */ + public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray(); + /** + * The score to ascribe to each job, keyed by job priority. + */ + public final SparseIntArray FALLBACK_FLEXIBILITY_DEADLINE_SCORES = new SparseIntArray(); + /** + * How much additional time to increase the fallback deadline by based on the app's current + * job run score. Values are in + * milliseconds and keyed by job priority. + */ + public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS = + new SparseLongArray(); + + FcConfig() { + // Copy the values from the DEFAULT_* data structures to avoid accidentally modifying + // the DEFAULT_* data structures in other parts of the code. + for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.size(); ++i) { + FALLBACK_FLEXIBILITY_DEADLINES.put( + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.keyAt(i), + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.valueAt(i)); + } + for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.size(); ++i) { + FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put( + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.keyAt(i), + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.valueAt(i)); + } + for (int i = 0; + i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.size(); + ++i) { + FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.put( + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .keyAt(i), + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS + .valueAt(i)); + } + for (int i = 0; i < DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.size(); ++i) { + PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.put( + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.keyAt(i), + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.valueAt(i)); + } + } @GuardedBy("mLock") public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { + // TODO(257322915): add appropriate minimums and maximums to constants when parsing switch (key) { case KEY_APPLIED_CONSTRAINTS: APPLIED_CONSTRAINTS = @@ -1034,6 +1332,33 @@ public final class FlexibilityController extends StateController { properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS); if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) { mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS; + } + break; + case KEY_FALLBACK_FLEXIBILITY_DEADLINES: + if (parsePriorityToLongKeyValueString( + properties.getString(key, null), + FALLBACK_FLEXIBILITY_DEADLINES, + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES)) { + mFallbackFlexibilityDeadlines = FALLBACK_FLEXIBILITY_DEADLINES; + mShouldReevaluateConstraints = true; + } + break; + case KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES: + if (parsePriorityToIntKeyValueString( + properties.getString(key, null), + FALLBACK_FLEXIBILITY_DEADLINE_SCORES, + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES)) { + mFallbackFlexibilityDeadlineScores = FALLBACK_FLEXIBILITY_DEADLINE_SCORES; + mShouldReevaluateConstraints = true; + } + break; + case KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS: + if (parsePriorityToLongKeyValueString( + properties.getString(key, null), + FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS, + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS)) { + mFallbackFlexibilityAdditionalScoreTimeFactors = + FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS; mShouldReevaluateConstraints = true; } break; @@ -1056,25 +1381,69 @@ public final class FlexibilityController extends StateController { mShouldReevaluateConstraints = true; } break; - case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS: - String dropPercentString = properties.getString(key, ""); - PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = - parsePercentToDropString(dropPercentString); - if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null - && !Arrays.equals(mPercentToDropConstraints, - PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) { - mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; + case KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS: + if (parsePercentToDropKeyValueString( + properties.getString(key, null), + PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS)) { + mPercentsToDropConstraints = PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; mShouldReevaluateConstraints = true; } break; } } - private int[] parsePercentToDropString(String s) { - String[] dropPercentString = s.split(","); + private boolean parsePercentToDropKeyValueString(@Nullable String s, + SparseArray<int[]> into, SparseArray<int[]> defaults) { + final KeyValueListParser priorityParser = new KeyValueListParser(','); + try { + priorityParser.setString(s); + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Bad percent to drop key value string given", e); + // Clear the string and continue with the defaults. + priorityParser.setString(null); + } + + final int[] oldMax = into.get(PRIORITY_MAX); + final int[] oldHigh = into.get(PRIORITY_HIGH); + final int[] oldDefault = into.get(PRIORITY_DEFAULT); + final int[] oldLow = into.get(PRIORITY_LOW); + final int[] oldMin = into.get(PRIORITY_MIN); + + final int[] newMax = parsePercentToDropString(priorityParser.getString( + String.valueOf(PRIORITY_MAX), null)); + final int[] newHigh = parsePercentToDropString(priorityParser.getString( + String.valueOf(PRIORITY_HIGH), null)); + final int[] newDefault = parsePercentToDropString(priorityParser.getString( + String.valueOf(PRIORITY_DEFAULT), null)); + final int[] newLow = parsePercentToDropString(priorityParser.getString( + String.valueOf(PRIORITY_LOW), null)); + final int[] newMin = parsePercentToDropString(priorityParser.getString( + String.valueOf(PRIORITY_MIN), null)); + + into.put(PRIORITY_MAX, newMax == null ? defaults.get(PRIORITY_MAX) : newMax); + into.put(PRIORITY_HIGH, newHigh == null ? defaults.get(PRIORITY_HIGH) : newHigh); + into.put(PRIORITY_DEFAULT, + newDefault == null ? defaults.get(PRIORITY_DEFAULT) : newDefault); + into.put(PRIORITY_LOW, newLow == null ? defaults.get(PRIORITY_LOW) : newLow); + into.put(PRIORITY_MIN, newMin == null ? defaults.get(PRIORITY_MIN) : newMin); + + return !Arrays.equals(oldMax, into.get(PRIORITY_MAX)) + || !Arrays.equals(oldHigh, into.get(PRIORITY_HIGH)) + || !Arrays.equals(oldDefault, into.get(PRIORITY_DEFAULT)) + || !Arrays.equals(oldLow, into.get(PRIORITY_LOW)) + || !Arrays.equals(oldMin, into.get(PRIORITY_MIN)); + } + + @Nullable + private int[] parsePercentToDropString(@Nullable String s) { + if (s == null || s.isEmpty()) { + return null; + } + final String[] dropPercentString = s.split("\\|"); int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)]; if (dropPercentInt.length != dropPercentString.length) { - return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; + return null; } int prevPercent = 0; for (int i = 0; i < dropPercentString.length; i++) { @@ -1083,11 +1452,15 @@ public final class FlexibilityController extends StateController { Integer.parseInt(dropPercentString[i]); } catch (NumberFormatException ex) { Slog.e(TAG, "Provided string was improperly formatted.", ex); - return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; + return null; } if (dropPercentInt[i] < prevPercent) { Slog.wtf(TAG, "Percents to drop constraints were not in increasing order."); - return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; + return null; + } + if (dropPercentInt[i] > 100) { + Slog.e(TAG, "Found % over 100"); + return null; } prevPercent = dropPercentInt[i]; } @@ -1095,6 +1468,102 @@ public final class FlexibilityController extends StateController { return dropPercentInt; } + /** + * Parses the input string, expecting it to a key-value string where the keys are job + * priorities, and replaces everything in {@code into} with the values from the string, + * or the default values if the string contains none. + * + * Returns true if any values changed. + */ + private boolean parsePriorityToIntKeyValueString(@Nullable String s, + SparseIntArray into, SparseIntArray defaults) { + final KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(s); + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Bad string given", e); + // Clear the string and continue with the defaults. + parser.setString(null); + } + + final int oldMax = into.get(PRIORITY_MAX); + final int oldHigh = into.get(PRIORITY_HIGH); + final int oldDefault = into.get(PRIORITY_DEFAULT); + final int oldLow = into.get(PRIORITY_LOW); + final int oldMin = into.get(PRIORITY_MIN); + + final int newMax = parser.getInt(String.valueOf(PRIORITY_MAX), + defaults.get(PRIORITY_MAX)); + final int newHigh = parser.getInt(String.valueOf(PRIORITY_HIGH), + defaults.get(PRIORITY_HIGH)); + final int newDefault = parser.getInt(String.valueOf(PRIORITY_DEFAULT), + defaults.get(PRIORITY_DEFAULT)); + final int newLow = parser.getInt(String.valueOf(PRIORITY_LOW), + defaults.get(PRIORITY_LOW)); + final int newMin = parser.getInt(String.valueOf(PRIORITY_MIN), + defaults.get(PRIORITY_MIN)); + + into.put(PRIORITY_MAX, newMax); + into.put(PRIORITY_HIGH, newHigh); + into.put(PRIORITY_DEFAULT, newDefault); + into.put(PRIORITY_LOW, newLow); + into.put(PRIORITY_MIN, newMin); + + return oldMax != newMax + || oldHigh != newHigh + || oldDefault != newDefault + || oldLow != newLow + || oldMin != newMin; + } + + /** + * Parses the input string, expecting it to a key-value string where the keys are job + * priorities, and replaces everything in {@code into} with the values from the string, + * or the default values if the string contains none. + * + * Returns true if any values changed. + */ + private boolean parsePriorityToLongKeyValueString(@Nullable String s, + SparseLongArray into, SparseLongArray defaults) { + final KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(s); + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Bad string given", e); + // Clear the string and continue with the defaults. + parser.setString(null); + } + + final long oldMax = into.get(PRIORITY_MAX); + final long oldHigh = into.get(PRIORITY_HIGH); + final long oldDefault = into.get(PRIORITY_DEFAULT); + final long oldLow = into.get(PRIORITY_LOW); + final long oldMin = into.get(PRIORITY_MIN); + + final long newMax = parser.getLong(String.valueOf(PRIORITY_MAX), + defaults.get(PRIORITY_MAX)); + final long newHigh = parser.getLong(String.valueOf(PRIORITY_HIGH), + defaults.get(PRIORITY_HIGH)); + final long newDefault = parser.getLong(String.valueOf(PRIORITY_DEFAULT), + defaults.get(PRIORITY_DEFAULT)); + final long newLow = parser.getLong(String.valueOf(PRIORITY_LOW), + defaults.get(PRIORITY_LOW)); + final long newMin = parser.getLong(String.valueOf(PRIORITY_MIN), + defaults.get(PRIORITY_MIN)); + + into.put(PRIORITY_MAX, newMax); + into.put(PRIORITY_HIGH, newHigh); + into.put(PRIORITY_DEFAULT, newDefault); + into.put(PRIORITY_LOW, newLow); + into.put(PRIORITY_MIN, newMin); + + return oldMax != newMax + || oldHigh != newHigh + || oldDefault != newDefault + || oldLow != newLow + || oldMin != newMin; + } + private void dump(IndentingPrintWriter pw) { pw.println(); pw.print(FlexibilityController.class.getSimpleName()); @@ -1111,10 +1580,15 @@ public final class FlexibilityController extends StateController { pw.println(")"); pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println(); pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println(); + pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINES, FALLBACK_FLEXIBILITY_DEADLINES).println(); + pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES, + FALLBACK_FLEXIBILITY_DEADLINE_SCORES).println(); + pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS, + FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS).println(); pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS, MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println(); - pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, - PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println(); + pw.print(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS).println(); pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println(); pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println(); pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) @@ -1171,7 +1645,21 @@ public final class FlexibilityController extends StateController { pw.println(mPowerAllowlistedApps); pw.println(); - mFlexibilityTracker.dump(pw, predicate); + mFlexibilityTracker.dump(pw, predicate, nowElapsed); + + pw.println(); + pw.println("Job scores:"); + pw.increaseIndent(); + mJobScoreTrackers.forEach((uid, pkgName, jobScoreTracker) -> { + pw.print(uid); + pw.print("/"); + pw.print(pkgName); + pw.print(": "); + jobScoreTracker.dump(pw, nowElapsed); + pw.println(); + }); + pw.decreaseIndent(); + pw.println(); mFlexibilityAlarmQueue.dump(pw); } diff --git a/config/preloaded-classes b/config/preloaded-classes index c49971eb68ae..11b24f53ceaf 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6172,8 +6172,6 @@ android.os.VibratorInfo$FrequencyProfile$1 android.os.VibratorInfo$FrequencyProfile android.os.VibratorInfo android.os.VibratorManager -android.os.VintfObject -android.os.VintfRuntimeInfo android.os.WorkSource$1 android.os.WorkSource$WorkChain$1 android.os.WorkSource$WorkChain diff --git a/core/api/current.txt b/core/api/current.txt index 4e61b0aaac62..109170615c94 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -195,6 +195,7 @@ package android { field public static final String MANAGE_DEVICE_POLICY_SYSTEM_APPS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS"; field public static final String MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS"; field public static final String MANAGE_DEVICE_POLICY_SYSTEM_UPDATES = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES"; + field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"; field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME"; field public static final String MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING = "android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING"; field public static final String MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER = "android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER"; @@ -12318,6 +12319,7 @@ package android.content.pm { method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); + method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean); method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle); method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); @@ -17875,6 +17877,7 @@ package android.graphics.text { field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2 field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0 field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1 + field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2 field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1 field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0 } @@ -22153,16 +22156,17 @@ package android.media { method public void onJetUserIdUpdate(android.media.JetPlayer, int, int); } - @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator { + @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable { method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec); - method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(); - method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener); - method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec); + method public void close(); + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int); + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener); + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec); + method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release(); method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec); - method @FlaggedApi("android.media.audio.loudness_configurator_api") public void setAudioTrack(@Nullable android.media.AudioTrack); } - @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener { + @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecController.OnLoudnessCodecUpdateListener { method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public default android.os.Bundle onLoudnessCodecUpdate(@NonNull android.media.MediaCodec, @NonNull android.os.Bundle); } @@ -23316,6 +23320,8 @@ package android.media { field public static final String KEY_AUDIO_SESSION_ID = "audio-session-id"; field public static final String KEY_BITRATE_MODE = "bitrate-mode"; field public static final String KEY_BIT_RATE = "bitrate"; + field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size"; + field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE = "buffer-batch-threshold-output-size"; field public static final String KEY_CAPTION_SERVICE_NUMBER = "caption-service-number"; field public static final String KEY_CAPTURE_RATE = "capture-rate"; field public static final String KEY_CHANNEL_COUNT = "channel-count"; @@ -25472,34 +25478,34 @@ package android.media.audiofx { field public short preset; } - public class Virtualizer extends android.media.audiofx.AudioEffect { - ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException; - method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public boolean getStrengthSupported(); - method public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener); - method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - method public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; - field public static final int PARAM_STRENGTH = 1; // 0x1 - field public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0 - field public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1 - field public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2 - field public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0 - field public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3 + @Deprecated public class Virtualizer extends android.media.audiofx.AudioEffect { + ctor @Deprecated public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public boolean getStrengthSupported(); + method @Deprecated public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener); + method @Deprecated public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + method @Deprecated public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException; + field @Deprecated public static final int PARAM_STRENGTH = 1; // 0x1 + field @Deprecated public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0 + field @Deprecated public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1 + field @Deprecated public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2 + field @Deprecated public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0 + field @Deprecated public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3 } - public static interface Virtualizer.OnParameterChangeListener { - method public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short); + @Deprecated public static interface Virtualizer.OnParameterChangeListener { + method @Deprecated public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short); } - public static class Virtualizer.Settings { - ctor public Virtualizer.Settings(); - ctor public Virtualizer.Settings(String); - field public short strength; + @Deprecated public static class Virtualizer.Settings { + ctor @Deprecated public Virtualizer.Settings(); + ctor @Deprecated public Virtualizer.Settings(String); + field @Deprecated public short strength; } public class Visualizer { @@ -42218,9 +42224,11 @@ package android.telecom { method public android.graphics.drawable.Icon getIcon(); method public CharSequence getLabel(); method public CharSequence getShortDescription(); + method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public java.util.Set<android.telecom.PhoneAccountHandle> getSimultaneousCallingRestriction(); method public android.net.Uri getSubscriptionAddress(); method public java.util.List<java.lang.String> getSupportedUriSchemes(); method public boolean hasCapabilities(int); + method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public boolean hasSimultaneousCallingRestriction(); method public boolean isEnabled(); method public boolean supportsUriScheme(String); method public android.telecom.PhoneAccount.Builder toBuilder(); @@ -42261,12 +42269,14 @@ package android.telecom { ctor public PhoneAccount.Builder(android.telecom.PhoneAccount); method public android.telecom.PhoneAccount.Builder addSupportedUriScheme(String); method public android.telecom.PhoneAccount build(); + method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public android.telecom.PhoneAccount.Builder clearSimultaneousCallingRestriction(); method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri); method public android.telecom.PhoneAccount.Builder setCapabilities(int); method public android.telecom.PhoneAccount.Builder setExtras(android.os.Bundle); method public android.telecom.PhoneAccount.Builder setHighlightColor(int); method public android.telecom.PhoneAccount.Builder setIcon(android.graphics.drawable.Icon); method public android.telecom.PhoneAccount.Builder setShortDescription(CharSequence); + method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public android.telecom.PhoneAccount.Builder setSimultaneousCallingRestriction(@NonNull java.util.Set<android.telecom.PhoneAccountHandle>); method public android.telecom.PhoneAccount.Builder setSubscriptionAddress(android.net.Uri); method public android.telecom.PhoneAccount.Builder setSupportedUriSchemes(java.util.List<java.lang.String>); } @@ -46947,6 +46957,7 @@ package android.text { field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP; field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL; field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER; + field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2 field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1 field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index debf1bfdfc8c..8a9f1c40bd43 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3959,6 +3959,7 @@ package android.content.pm { method public void setInstallAsInstantApp(boolean); method public void setInstallAsVirtualPreload(); method public void setRequestDowngrade(boolean); + method @FlaggedApi("android.content.pm.recoverability_detection") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int); method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged(); } @@ -4127,6 +4128,9 @@ package android.content.pm { field public static final int ROLLBACK_DATA_POLICY_RESTORE = 0; // 0x0 field public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; // 0x2 field public static final int ROLLBACK_DATA_POLICY_WIPE = 1; // 0x1 + field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1 + field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0 + field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2 field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0; // 0x0 field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1; // 0x1 field public static final int SYSTEM_APP_STATE_INSTALLED = 2; // 0x2 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a866a34166f7..e8a4a829fd50 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -156,6 +156,7 @@ package android.app { field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6 field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8 field public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 32; // 0x20 + field public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 5; // 0x5 field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 field public static final int PROCESS_STATE_TOP = 2; // 0x2 field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff @@ -1209,6 +1210,10 @@ package android.content.res { package android.content.rollback { + public final class RollbackInfo implements android.os.Parcelable { + method @FlaggedApi("android.content.pm.recoverability_detection") public int getRollbackImpactLevel(); + } + public final class RollbackManager { method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void blockRollbackManager(long); method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b2c64756e4bf..9d20f3c47bb5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -712,6 +712,8 @@ public class ActivityManager { /** @hide Process is hosting a foreground service due to a system binding. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + @TestApi public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = ProcessStateEnum.BOUND_FOREGROUND_SERVICE; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4b2e93fda171..ea37e7fbcce1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1533,10 +1533,11 @@ public class AppOpsManager { public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING; /** - * Rapid clearing of notifications by a notification listener, see b/289080543 for details + * Rapid clearing of notifications by a notification listener * * @hide */ + // See b/289080543 for more details public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER; @@ -1547,9 +1548,24 @@ public class AppOpsManager { public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER = AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; + /** + * Whether the app has enabled to receive the icon overlay for fetching archived apps. + * + * @hide + */ + public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY; + + /** + * Whether the app has enabled compatibility support for unarchival. + * + * @hide + */ + public static final int OP_UNARCHIVAL_CONFIRMATION = + AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 144; + public static final int _NUM_OP = 146; /** * All app ops represented as strings. @@ -1699,6 +1715,8 @@ public class AppOpsManager { OPSTR_ENABLE_MOBILE_DATA_BY_USER, OPSTR_RESERVED_FOR_TESTING, OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + OPSTR_ARCHIVE_ICON_OVERLAY, + OPSTR_UNARCHIVAL_CONFIRMATION, }) public @interface AppOpString {} @@ -2039,6 +2057,20 @@ public class AppOpsManager { public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control"; /** + * Whether the app has enabled to receive the icon overlay for fetching archived apps. + * + * @hide + */ + public static final String OPSTR_ARCHIVE_ICON_OVERLAY = "android:archive_icon_overlay"; + + /** + * Whether the app has enabled compatibility support for unarchival. + * + * @hide + */ + public static final String OPSTR_UNARCHIVAL_CONFIRMATION = "android:unarchival_support"; + + /** * 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 @@ -2373,10 +2405,11 @@ public class AppOpsManager { "android:reserved_for_testing"; /** - * Rapid clearing of notifications by a notification listener, see b/289080543 for details + * Rapid clearing of notifications by a notification listener * * @hide */ + // See b/289080543 for more details @SystemApi @FlaggedApi(FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED) public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = @@ -2502,6 +2535,8 @@ public class AppOpsManager { OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OP_MEDIA_ROUTING_CONTROL, OP_READ_SYSTEM_GRAMMATICAL_GENDER, + OP_ARCHIVE_ICON_OVERLAY, + OP_UNARCHIVAL_CONFIRMATION, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2958,6 +2993,12 @@ public class AppOpsManager { OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER") .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER) .build(), + new AppOpInfo.Builder(OP_ARCHIVE_ICON_OVERLAY, OPSTR_ARCHIVE_ICON_OVERLAY, + "ARCHIVE_ICON_OVERLAY") + .setDefaultMode(MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION, + "UNARCHIVAL_CONFIRMATION") + .setDefaultMode(MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops @@ -3092,7 +3133,7 @@ public class AppOpsManager { /** * Retrieve the permission associated with an operation, or null if there is not one. - * + * @param op The operation name. * * @hide diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 34c44f9489d5..4f1db7d3784a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -4032,7 +4032,8 @@ public class ApplicationPackageManager extends PackageManager { private Drawable getArchivedAppIcon(String packageName) { try { return new BitmapDrawable(null, - mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()))); + mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()), + mContext.getPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index afa513dbaaef..c6712c044539 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -413,7 +413,9 @@ public final class ApplicationStartInfo implements Parcelable { * @hide */ public void setIntent(Intent startIntent) { - mStartIntent = startIntent; + if (startIntent != null) { + mStartIntent = startIntent.maybeStripForHistory(); + } } /** @@ -548,6 +550,8 @@ public final class ApplicationStartInfo implements Parcelable { /** * The intent used to launch the application. * + * <p class="note"> Note: Intent is stripped and does not include extras.</p> + * * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p> */ @SuppressLint("IntentBuilderName") @@ -662,6 +666,7 @@ public final class ApplicationStartInfo implements Parcelable { private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp"; private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key"; private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts"; + private static final String PROTO_SERIALIZER_ATTRIBUTE_INTENT = "intent"; /** * Write to a protocol buffer output stream. Protocol buffer message definition at {@link @@ -702,10 +707,17 @@ public final class ApplicationStartInfo implements Parcelable { } proto.write(ApplicationStartInfoProto.START_TYPE, mStartType); if (mStartIntent != null) { - Parcel parcel = Parcel.obtain(); - mStartIntent.writeToParcel(parcel, 0); - proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall()); - parcel.recycle(); + ByteArrayOutputStream intentBytes = new ByteArrayOutputStream(); + ObjectOutputStream intentOut = new ObjectOutputStream(intentBytes); + TypedXmlSerializer serializer = Xml.resolveSerializer(intentOut); + serializer.startDocument(null, true); + serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT); + mStartIntent.saveToXml(serializer); + serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT); + serializer.endDocument(); + proto.write(ApplicationStartInfoProto.START_INTENT, + intentBytes.toByteArray()); + intentOut.close(); } proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); proto.end(token); @@ -772,15 +784,17 @@ public final class ApplicationStartInfo implements Parcelable { mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE); break; case (int) ApplicationStartInfoProto.START_INTENT: - byte[] startIntentBytes = proto.readBytes( - ApplicationStartInfoProto.START_INTENT); - if (startIntentBytes.length > 0) { - Parcel parcel = Parcel.obtain(); - parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length); - parcel.setDataPosition(0); - mStartIntent = Intent.CREATOR.createFromParcel(parcel); - parcel.recycle(); + ByteArrayInputStream intentBytes = new ByteArrayInputStream(proto.readBytes( + ApplicationStartInfoProto.START_INTENT)); + ObjectInputStream intentIn = new ObjectInputStream(intentBytes); + try { + TypedXmlPullParser parser = Xml.resolvePullParser(intentIn); + XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_INTENT); + mStartIntent = Intent.restoreFromXml(parser); + } catch (XmlPullParserException e) { + // Intent lost } + intentIn.close(); break; case (int) ApplicationStartInfoProto.LAUNCH_MODE: mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE); diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig new file mode 100644 index 000000000000..029b93ab4534 --- /dev/null +++ b/core/java/android/app/background_install_control_manager.aconfig @@ -0,0 +1,9 @@ +package: "android.app" + +flag { + namespace: "background_install_control" + name: "bic_client" + description: "System API for background install control." + is_fixed_read_only: true + bug: "287507984" +} diff --git a/core/java/android/content/pm/ArchivedActivityInfo.java b/core/java/android/content/pm/ArchivedActivityInfo.java index 1faa4373d88f..166d26503625 100644 --- a/core/java/android/content/pm/ArchivedActivityInfo.java +++ b/core/java/android/content/pm/ArchivedActivityInfo.java @@ -91,26 +91,31 @@ public final class ArchivedActivityInfo { * @hide */ public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) { - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - - } - Bitmap bitmap; - if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { - // Needed for drawables that are just a single color. - bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); } else { - bitmap = + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + // Needed for drawables that are just a single color. + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = Bitmap.createBitmap( - drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); } - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) { + if (iconSize <= 0) { + return bitmap; + } + + if (bitmap.getWidth() < iconSize || bitmap.getHeight() < iconSize + || bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) { var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true); if (scaledBitmap != bitmap) { bitmap.recycle(); @@ -235,7 +240,7 @@ public final class ArchivedActivityInfo { } @DataClass.Generated( - time = 1698789991876L, + time = 1705615445673L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivityInfo.java", inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)") diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index a97de6368b8c..62db65f15df3 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -128,4 +128,6 @@ interface ILauncherApps { /** Unregister a callback, so that it won't be called when LauncherApps dumps. */ void unRegisterDumpCallback(IDumpCallback cb); + + void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 6dc8d4738c87..380de965b143 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -840,7 +840,7 @@ interface IPackageManager { ArchivedPackageParcel getArchivedPackage(in String packageName, int userId); - Bitmap getArchivedAppIcon(String packageName, in UserHandle user); + Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName); boolean isAppArchivable(String packageName, in UserHandle user); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 1d2b1aff46bc..50be983ec938 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1801,6 +1801,31 @@ public class LauncherApps { } } + /** + * Enable or disable different archive compatibility options of the launcher. + * + * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware + * that a certain app is archived. True by default. + * Launchers might want to disable this operation if they want to provide custom user experience + * to differentiate archived apps. + * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when + * they click an archived app, which explains that the app will be downloaded and restored in + * the background. True by default. + * Launchers might want to disable this operation if they provide sufficient, alternative user + * guidance to highlight that an unarchival is starting and ongoing once an archived app is + * tapped. E.g., this could be achieved by showing the unarchival progress around the icon. + */ + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) + public void setArchiveCompatibilityOptions(boolean enableIconOverlay, + boolean enableUnarchivalConfirmation) { + try { + mService.setArchiveCompatibilityOptions(enableIconOverlay, + enableUnarchivalConfirmation); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** @return position in mCallbacks for callback or -1 if not present. */ private int findCallbackLocked(Callback callback) { if (callback == null) { diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index f0efed97d8ed..22926febfb2c 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2693,6 +2693,8 @@ public class PackageInstaller { /** @hide */ public long rollbackLifetimeMillis = 0; /** {@hide} */ + public int rollbackImpactLevel = PackageManager.ROLLBACK_USER_IMPACT_LOW; + /** {@hide} */ public boolean forceQueryableOverride; /** {@hide} */ public int requireUserAction = USER_ACTION_UNSPECIFIED; @@ -2749,6 +2751,7 @@ public class PackageInstaller { } rollbackDataPolicy = source.readInt(); rollbackLifetimeMillis = source.readLong(); + rollbackImpactLevel = source.readInt(); requireUserAction = source.readInt(); packageSource = source.readInt(); applicationEnabledSettingPersistent = source.readBoolean(); @@ -2783,6 +2786,7 @@ public class PackageInstaller { ret.dataLoaderParams = dataLoaderParams; ret.rollbackDataPolicy = rollbackDataPolicy; ret.rollbackLifetimeMillis = rollbackLifetimeMillis; + ret.rollbackImpactLevel = rollbackImpactLevel; ret.requireUserAction = requireUserAction; ret.packageSource = packageSource; ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; @@ -3121,6 +3125,28 @@ public class PackageInstaller { } /** + * rollbackImpactLevel is a measure of impact a rollback has on the user. This can take one + * of 3 values: + * <ul> + * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_LOW} (default)</li> + * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_HIGH} (1)</li> + * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_ONLY_MANUAL} (2)</li> + * </ul> + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) + @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION) + public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) { + if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { + throw new IllegalArgumentException( + "Can't set rollbackImpactLevel when rollback is not enabled"); + } + rollbackImpactLevel = impactLevel; + } + + /** * @deprecated use {@link #setRequestDowngrade(boolean)}. * {@hide} */ @@ -3493,6 +3519,7 @@ public class PackageInstaller { pw.printPair("dataLoaderParams", dataLoaderParams); pw.printPair("rollbackDataPolicy", rollbackDataPolicy); pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis); + pw.printPair("rollbackImpactLevel", rollbackImpactLevel); pw.printPair("applicationEnabledSettingPersistent", applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); @@ -3536,6 +3563,7 @@ public class PackageInstaller { } dest.writeInt(rollbackDataPolicy); dest.writeLong(rollbackLifetimeMillis); + dest.writeInt(rollbackImpactLevel); dest.writeInt(requireUserAction); dest.writeInt(packageSource); dest.writeBoolean(applicationEnabledSettingPersistent); @@ -3734,6 +3762,9 @@ public class PackageInstaller { public long rollbackLifetimeMillis; /** {@hide} */ + public int rollbackImpactLevel; + + /** {@hide} */ public int requireUserAction; /** {@hide} */ @@ -3801,6 +3832,7 @@ public class PackageInstaller { isPreapprovalRequested = source.readBoolean(); rollbackDataPolicy = source.readInt(); rollbackLifetimeMillis = source.readLong(); + rollbackImpactLevel = source.readInt(); createdMillis = source.readLong(); requireUserAction = source.readInt(); installerUid = source.readInt(); @@ -4438,6 +4470,7 @@ public class PackageInstaller { dest.writeBoolean(isPreapprovalRequested); dest.writeInt(rollbackDataPolicy); dest.writeLong(rollbackLifetimeMillis); + dest.writeInt(rollbackImpactLevel); dest.writeLong(createdMillis); dest.writeInt(requireUserAction); dest.writeInt(installerUid); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index aabbe698703c..4724e866f094 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1501,6 +1501,44 @@ public abstract class PackageManager { public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; /** @hide */ + @IntDef(prefix = {"ROLLBACK_USER_IMPACT_"}, value = { + ROLLBACK_USER_IMPACT_LOW, + ROLLBACK_USER_IMPACT_HIGH, + ROLLBACK_USER_IMPACT_ONLY_MANUAL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RollbackImpactLevel {} + + /** + * Rollback will be performed automatically in response to native crashes on startup or + * persistent service crashes. More suitable for apps that do not store any user data. + * + * @hide + */ + @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION) + public static final int ROLLBACK_USER_IMPACT_LOW = 0; + + /** + * Rollback will be performed automatically only when the device is found to be unrecoverable. + * More suitable for apps that store user data and have higher impact on user. + * + * @hide + */ + @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION) + public static final int ROLLBACK_USER_IMPACT_HIGH = 1; + + /** + * Rollback will not be performed automatically. It can be triggered externally. + * + * @hide + */ + @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION) + public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; + + /** @hide */ @IntDef(flag = true, prefix = { "INSTALL_" }, value = { INSTALL_REPLACE_EXISTING, INSTALL_ALLOW_TEST, diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index a2cd3e153b3e..e4e9fbaf2c55 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -146,3 +146,10 @@ flag { bug: "281848623" } +flag { + name: "recoverability_detection" + namespace: "package_manager_service" + description: "Feature flag to enable recoverability detection feature. It includes GMS core rollback and improvements to rescue party." + bug: "291135724" + is_fixed_read_only: true +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 7c5d3054c945..5bfc012844f8 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -77,4 +77,11 @@ flag { namespace: "profile_experiences" description: "Move the quiet mode operations, happening on a background thread today, to a separate thread." bug: "320483504" +} + +flag { + name: "enable_private_space_autolock_on_restarts" + namespace: "profile_experiences" + description: "Enable auto-locking private space on device restarts" + bug: "296993385" }
\ No newline at end of file diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java index a363718a8b1d..d128055fec6d 100644 --- a/core/java/android/content/rollback/RollbackInfo.java +++ b/core/java/android/content/rollback/RollbackInfo.java @@ -16,8 +16,12 @@ package android.content.rollback; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.pm.Flags; +import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Parcel; import android.os.Parcelable; @@ -25,17 +29,14 @@ import android.os.Parcelable; import java.util.List; /** - * Information about a set of packages that can be, or already have been - * rolled back together. + * Information about a set of packages that can be, or already have been rolled back together. * * @hide */ @SystemApi public final class RollbackInfo implements Parcelable { - /** - * A unique identifier for the rollback. - */ + /** A unique identifier for the rollback. */ private final int mRollbackId; private final List<PackageRollbackInfo> mPackages; @@ -44,15 +45,39 @@ public final class RollbackInfo implements Parcelable { private final boolean mIsStaged; private int mCommittedSessionId; + private int mRollbackImpactLevel; /** @hide */ - public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged, - List<VersionedPackage> causePackages, int committedSessionId) { + public RollbackInfo( + int rollbackId, + List<PackageRollbackInfo> packages, + boolean isStaged, + List<VersionedPackage> causePackages, + int committedSessionId, + @PackageManager.RollbackImpactLevel int rollbackImpactLevel) { this.mRollbackId = rollbackId; this.mPackages = packages; this.mIsStaged = isStaged; this.mCausePackages = causePackages; this.mCommittedSessionId = committedSessionId; + this.mRollbackImpactLevel = rollbackImpactLevel; + } + + /** @hide */ + public RollbackInfo( + int rollbackId, + List<PackageRollbackInfo> packages, + boolean isStaged, + List<VersionedPackage> causePackages, + int committedSessionId) { + // If impact level is not set default to 0 + this( + rollbackId, + packages, + isStaged, + causePackages, + committedSessionId, + PackageManager.ROLLBACK_USER_IMPACT_LOW); } private RollbackInfo(Parcel in) { @@ -61,34 +86,28 @@ public final class RollbackInfo implements Parcelable { mIsStaged = in.readBoolean(); mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR); mCommittedSessionId = in.readInt(); + mRollbackImpactLevel = in.readInt(); } - /** - * Returns a unique identifier for this rollback. - */ + /** Returns a unique identifier for this rollback. */ public int getRollbackId() { return mRollbackId; } - /** - * Returns the list of package that are rolled back. - */ + /** Returns the list of package that are rolled back. */ @NonNull public List<PackageRollbackInfo> getPackages() { return mPackages; } - /** - * Returns true if this rollback requires reboot to take effect after - * being committed. - */ + /** Returns true if this rollback requires reboot to take effect after being committed. */ public boolean isStaged() { return mIsStaged; } /** - * Returns the session ID for the committed rollback for staged rollbacks. - * Only applicable for rollbacks that have been committed. + * Returns the session ID for the committed rollback for staged rollbacks. Only applicable for + * rollbacks that have been committed. */ public int getCommittedSessionId() { return mCommittedSessionId; @@ -96,6 +115,7 @@ public final class RollbackInfo implements Parcelable { /** * Sets the session ID for the committed rollback for staged rollbacks. + * * @hide */ public void setCommittedSessionId(int sessionId) { @@ -103,15 +123,40 @@ public final class RollbackInfo implements Parcelable { } /** - * Gets the list of package versions that motivated this rollback. - * As provided to {@link #commitRollback} when the rollback was committed. - * This is only applicable for rollbacks that have been committed. + * Gets the list of package versions that motivated this rollback. As provided to {@link + * #commitRollback} when the rollback was committed. This is only applicable for rollbacks that + * have been committed. */ @NonNull public List<VersionedPackage> getCausePackages() { return mCausePackages; } + /** + * Get rollback impact level. Refer {@link + * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info + * on impact level. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION) + public @PackageManager.RollbackImpactLevel int getRollbackImpactLevel() { + return mRollbackImpactLevel; + } + + /** + * Set rollback impact level. Refer {@link + * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info + * on impact level. + * + * @hide + */ + public void setRollbackImpactLevel( + @PackageManager.RollbackImpactLevel int rollbackImpactLevel) { + mRollbackImpactLevel = rollbackImpactLevel; + } + @Override public int describeContents() { return 0; @@ -124,16 +169,17 @@ public final class RollbackInfo implements Parcelable { out.writeBoolean(mIsStaged); out.writeTypedList(mCausePackages); out.writeInt(mCommittedSessionId); + out.writeInt(mRollbackImpactLevel); } public static final @android.annotation.NonNull Parcelable.Creator<RollbackInfo> CREATOR = new Parcelable.Creator<RollbackInfo>() { - public RollbackInfo createFromParcel(Parcel in) { - return new RollbackInfo(in); - } - - public RollbackInfo[] newArray(int size) { - return new RollbackInfo[size]; - } - }; + public RollbackInfo createFromParcel(Parcel in) { + return new RollbackInfo(in); + } + + public RollbackInfo[] newArray(int size) { + return new RollbackInfo[size]; + } + }; } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 8a4f678b52f2..35ae3c9e23c7 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -40,12 +40,15 @@ import android.os.Looper; import android.os.OperationCanceledException; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.util.Printer; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.NeverCompile; @@ -103,8 +106,14 @@ public final class SQLiteDatabase extends SQLiteClosable { // Stores reference to all databases opened in the current process. // (The referent Object is not used at this time.) // INVARIANT: Guarded by sActiveDatabases. + @GuardedBy("sActiveDatabases") private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>(); + // Tracks which database files are currently open. If a database file is opened more than + // once at any given moment, the associated databases are marked as "concurrent". + @GuardedBy("sActiveDatabases") + private static final OpenTracker sOpenTracker = new OpenTracker(); + // Thread-local for database sessions that belong to this database. // Each thread has its own database session. // INVARIANT: Immutable. @@ -510,6 +519,7 @@ public final class SQLiteDatabase extends SQLiteClosable { private void dispose(boolean finalized) { final SQLiteConnectionPool pool; + final String path; synchronized (mLock) { if (mCloseGuardLocked != null) { if (finalized) { @@ -520,10 +530,12 @@ public final class SQLiteDatabase extends SQLiteClosable { pool = mConnectionPoolLocked; mConnectionPoolLocked = null; + path = isInMemoryDatabase() ? null : getPath(); } if (!finalized) { synchronized (sActiveDatabases) { + sOpenTracker.close(path); sActiveDatabases.remove(this); } @@ -1132,6 +1144,74 @@ public final class SQLiteDatabase extends SQLiteClosable { } } + /** + * Track the number of times a database file has been opened. There is a primary connection + * associated with every open database, and these can contend with each other, leading to + * unexpected SQLiteDatabaseLockedException exceptions. The tracking here is only advisory: + * multiply-opened databases are logged but no other action is taken. + * + * This class is not thread-safe. + */ + private static class OpenTracker { + // The list of currently-open databases. This maps the database file to the number of + // currently-active opens. + private final ArrayMap<String, Integer> mOpens = new ArrayMap<>(); + + // The maximum number of concurrently open database paths that will be stored. Once this + // many paths have been recorded, further paths are logged but not saved. + private static final int MAX_RECORDED_PATHS = 20; + + // The list of databases that were ever concurrently opened. + private final ArraySet<String> mConcurrent = new ArraySet<>(); + + /** Return the canonical path. On error, just return the input path. */ + private static String normalize(String path) { + try { + return new File(path).toPath().toRealPath().toString(); + } catch (Exception e) { + // If there is an IO or security exception, just continue, using the input path. + return path; + } + } + + /** Return true if the path is currently open in another SQLiteDatabase instance. */ + void open(@Nullable String path) { + if (path == null) return; + path = normalize(path); + + Integer count = mOpens.get(path); + if (count == null || count == 0) { + mOpens.put(path, 1); + return; + } else { + mOpens.put(path, count + 1); + if (mConcurrent.size() < MAX_RECORDED_PATHS) { + mConcurrent.add(path); + } + Log.w(TAG, "multiple primary connections on " + path); + return; + } + } + + void close(@Nullable String path) { + if (path == null) return; + path = normalize(path); + Integer count = mOpens.get(path); + if (count == null || count <= 0) { + Log.e(TAG, "open database counting failure on " + path); + } else if (count == 1) { + // Implicitly set the count to zero, and make mOpens smaller. + mOpens.remove(path); + } else { + mOpens.put(path, count - 1); + } + } + + ArraySet<String> getConcurrentDatabasePaths() { + return new ArraySet<>(mConcurrent); + } + } + private void open() { try { try { @@ -1153,14 +1233,17 @@ public final class SQLiteDatabase extends SQLiteClosable { } private void openInner() { + final String path; synchronized (mLock) { assert mConnectionPoolLocked == null; mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked); mCloseGuardLocked.open("close"); + path = isInMemoryDatabase() ? null : getPath(); } synchronized (sActiveDatabases) { sActiveDatabases.put(this, null); + sOpenTracker.open(path); } } @@ -2345,6 +2428,17 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** + * Return list of databases that have been concurrently opened. + * @hide + */ + @VisibleForTesting + public static ArraySet<String> getConcurrentDatabasePaths() { + synchronized (sActiveDatabases) { + return sOpenTracker.getConcurrentDatabasePaths(); + } + } + + /** * Returns true if the new version code is greater than the current database version. * * @param newVersion The new version code. @@ -2766,6 +2860,19 @@ public final class SQLiteDatabase extends SQLiteClosable { dumpDatabaseDirectory(printer, new File(dir), isSystem); } } + + // Dump concurrently-opened database files, if any + final ArraySet<String> concurrent; + synchronized (sActiveDatabases) { + concurrent = sOpenTracker.getConcurrentDatabasePaths(); + } + if (concurrent.size() > 0) { + printer.println(""); + printer.println("Concurrently opened database files"); + for (String f : concurrent) { + printer.println(" " + f); + } + } } private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) { diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java index 4fc5131617b2..505655775239 100644 --- a/core/java/android/os/VintfObject.java +++ b/core/java/android/os/VintfObject.java @@ -31,6 +31,10 @@ public class VintfObject { private static final String LOG_TAG = "VintfObject"; + static { + System.loadLibrary("vintf_jni"); + } + /** * Slurps all device information (both manifests and both matrices) * and report them. diff --git a/core/java/android/os/VintfRuntimeInfo.java b/core/java/android/os/VintfRuntimeInfo.java index f17039ba9bf4..e729063d6763 100644 --- a/core/java/android/os/VintfRuntimeInfo.java +++ b/core/java/android/os/VintfRuntimeInfo.java @@ -28,6 +28,10 @@ public class VintfRuntimeInfo { private VintfRuntimeInfo() {} + static { + System.loadLibrary("vintf_jni"); + } + /** * @return /sys/fs/selinux/policyvers, via security_policyvers() native call * diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 39b6aeb814f6..8c7050176506 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -86,3 +86,11 @@ flag { description: "This flag is used to enabled the Wallet Role for all users on the device" bug: "283989236" } + +flag { + name: "signature_permission_allowlist_enabled" + is_fixed_read_only: true + namespace: "permissions" + description: "Enable signature permission allowlist" + bug: "308573169" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ab98c94cba73..d946430f18af 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3243,9 +3243,17 @@ public final class Settings { private static final String NAME_EQ_PLACEHOLDER = "name=?"; + // Cached values of queried settings. + // Key is the setting's name, value is the setting's value. // Must synchronize on 'this' to access mValues and mValuesVersion. private final ArrayMap<String, String> mValues = new ArrayMap<>(); + // Cached values for queried prefixes. + // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's + // name to a setting's value. The name string doesn't include the prefix. + // Must synchronize on 'this' to access. + private final ArrayMap<String, ArrayMap<String, String>> mPrefixToValues = new ArrayMap<>(); + private final Uri mUri; @UnsupportedAppUsage private final ContentProviderHolder mProviderHolder; @@ -3592,15 +3600,13 @@ public final class Settings { || applicationInfo.isSignedWithPlatformKey(); } - private ArrayMap<String, String> getStringsForPrefixStripPrefix( - ContentResolver cr, String prefix, String[] names) { + private Map<String, String> getStringsForPrefixStripPrefix( + ContentResolver cr, String prefix, List<String> names) { String namespace = prefix.substring(0, prefix.length() - 1); ArrayMap<String, String> keyValues = new ArrayMap<>(); int substringLength = prefix.length(); - int currentGeneration = -1; boolean needsGenerationTracker = false; - synchronized (NameValueCache.this) { final GenerationTracker generationTracker = mGenerationTrackers.get(prefix); if (generationTracker != null) { @@ -3614,40 +3620,24 @@ public final class Settings { // generation tracker and request a new one generationTracker.destroy(); mGenerationTrackers.remove(prefix); - for (int i = mValues.size() - 1; i >= 0; i--) { - String key = mValues.keyAt(i); - if (key.startsWith(prefix)) { - mValues.remove(key); - } - } + mPrefixToValues.remove(prefix); needsGenerationTracker = true; } else { - boolean prefixCached = mValues.containsKey(prefix); - if (prefixCached) { - if (DEBUG) { - Log.i(TAG, "Cache hit for prefix:" + prefix); - } - if (names.length > 0) { + final ArrayMap<String, String> cachedSettings = mPrefixToValues.get(prefix); + if (cachedSettings != null) { + if (!names.isEmpty()) { for (String name : names) { - // mValues can contain "null" values, need to use containsKey. - if (mValues.containsKey(name)) { + // The cache can contain "null" values, need to use containsKey. + if (cachedSettings.containsKey(name)) { keyValues.put( - name.substring(substringLength), - mValues.get(name)); + name, + cachedSettings.get(name)); } } } else { - for (int i = 0; i < mValues.size(); ++i) { - String key = mValues.keyAt(i); - // Explicitly exclude the prefix as it is only there to - // signal that the prefix has been cached. - if (key.startsWith(prefix) && !key.equals(prefix)) { - String value = mValues.valueAt(i); - keyValues.put( - key.substring(substringLength), - value); - } - } + keyValues.putAll(cachedSettings); + // Remove the hack added for the legacy behavior. + keyValues.remove(""); } return keyValues; } @@ -3657,7 +3647,6 @@ public final class Settings { needsGenerationTracker = true; } } - if (mCallListCommand == null) { // No list command specified, return empty map return keyValues; @@ -3702,20 +3691,23 @@ public final class Settings { } // All flags for the namespace - Map<String, String> flagsToValues = + HashMap<String, String> flagsToValues = (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class); + if (flagsToValues == null) { + return keyValues; + } // Only the flags requested by the caller - if (names.length > 0) { + if (!names.isEmpty()) { for (String name : names) { // flagsToValues can contain "null" values, need to use containsKey. - if (flagsToValues.containsKey(name)) { + final String key = Config.createCompositeName(namespace, name); + if (flagsToValues.containsKey(key)) { keyValues.put( - name.substring(substringLength), - flagsToValues.get(name)); + name, + flagsToValues.get(key)); } } } else { - keyValues.ensureCapacity(keyValues.size() + flagsToValues.size()); for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { keyValues.put( flag.getKey().substring(substringLength), @@ -3751,10 +3743,18 @@ public final class Settings { if (DEBUG) { Log.i(TAG, "Updating cache for prefix:" + prefix); } - // cache the complete list of flags for the namespace - mValues.putAll(flagsToValues); - // Adding the prefix as a signal that the prefix is cached. - mValues.put(prefix, null); + // Cache the complete list of flags for the namespace for bulk queries. + // In this cached list, the setting's name doesn't include the prefix. + ArrayMap<String, String> namesToValues = + new ArrayMap<>(flagsToValues.size() + 1); + for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { + namesToValues.put( + flag.getKey().substring(substringLength), + flag.getValue()); + } + // Legacy behavior, we return <"", null> when queried with name = "" + namesToValues.put("", null); + mPrefixToValues.put(prefix, namesToValues); } } return keyValues; @@ -19945,16 +19945,9 @@ public final class Settings { @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public static Map<String, String> getStrings(@NonNull ContentResolver resolver, @NonNull String namespace, @NonNull List<String> names) { - String[] compositeNames = new String[names.size()]; - for (int i = 0, size = names.size(); i < size; ++i) { - compositeNames[i] = createCompositeName(namespace, names.get(i)); - } - String prefix = createPrefix(namespace); - ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix( - resolver, prefix, compositeNames); - return keyValues; + return sNameValueCache.getStringsForPrefixStripPrefix(resolver, prefix, names); } /** @@ -20276,7 +20269,7 @@ public final class Settings { } } - private static String createCompositeName(@NonNull String namespace, @NonNull String name) { + static String createCompositeName(@NonNull String namespace, @NonNull String name) { Preconditions.checkNotNull(namespace); Preconditions.checkNotNull(name); var sb = new StringBuilder(namespace.length() + 1 + name.length()); diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index eca848ae1ea3..1ea80f146b0b 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -152,7 +152,8 @@ public abstract class Layout { /** @hide */ @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = { LineBreaker.JUSTIFICATION_MODE_NONE, - LineBreaker.JUSTIFICATION_MODE_INTER_WORD + LineBreaker.JUSTIFICATION_MODE_INTER_WORD, + LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER, }) @Retention(RetentionPolicy.SOURCE) public @interface JustificationMode {} @@ -168,6 +169,13 @@ public abstract class Layout { public static final int JUSTIFICATION_MODE_INTER_WORD = LineBreaker.JUSTIFICATION_MODE_INTER_WORD; + /** + * Value for justification mode indicating the text is justified by stretching letter spacing. + */ + @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION) + public static final int JUSTIFICATION_MODE_INTER_CHARACTER = + LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER; + /* * Line spacing multiplier for default line spacing. */ @@ -809,7 +817,7 @@ public abstract class Layout { getEllipsisStart(lineNum) + getEllipsisCount(lineNum), isFallbackLineSpacingEnabled()); if (justify) { - tl.justify(right - left - indentWidth); + tl.justify(mJustificationMode, right - left - indentWidth); } tl.draw(canvas, x, ltop, lbaseline, lbottom); } @@ -1058,7 +1066,7 @@ public abstract class Layout { getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { - tl.justify(getJustifyWidth(line)); + tl.justify(mJustificationMode, getJustifyWidth(line)); } tl.metrics(null, rectF, false, null); @@ -1794,7 +1802,7 @@ public abstract class Layout { getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { - tl.justify(getJustifyWidth(line)); + tl.justify(mJustificationMode, getJustifyWidth(line)); } final float width = tl.metrics(null, null, mUseBoundsForWidth, null); TextLine.recycle(tl); @@ -1882,7 +1890,7 @@ public abstract class Layout { getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { - tl.justify(getJustifyWidth(line)); + tl.justify(mJustificationMode, getJustifyWidth(line)); } final float width = tl.metrics(null, null, mUseBoundsForWidth, null); TextLine.recycle(tl); diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 2175b47e149e..224e5d8f7712 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -100,9 +100,25 @@ public class TextLine { // Additional width of whitespace for justification. This value is per whitespace, thus // the line width will increase by mAddedWidthForJustify x (number of stretchable whitespaces). - private float mAddedWidthForJustify; + private float mAddedWordSpacingInPx; + private float mAddedLetterSpacingInPx; private boolean mIsJustifying; + @VisibleForTesting + public float getAddedWordSpacingInPx() { + return mAddedWordSpacingInPx; + } + + @VisibleForTesting + public float getAddedLetterSpacingInPx() { + return mAddedLetterSpacingInPx; + } + + @VisibleForTesting + public boolean isJustifying() { + return mIsJustifying; + } + private final TextPaint mWorkPaint = new TextPaint(); private final TextPaint mActivePaint = new TextPaint(); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -259,7 +275,7 @@ public class TextLine { } } mTabs = tabStops; - mAddedWidthForJustify = 0; + mAddedWordSpacingInPx = 0; mIsJustifying = false; mEllipsisStart = ellipsisStart != ellipsisEnd ? ellipsisStart : 0; @@ -274,19 +290,42 @@ public class TextLine { * Justify the line to the given width. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void justify(float justifyWidth) { + public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) { int end = mLen; while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) { end--; } - final int spaces = countStretchableSpaces(0, end); - if (spaces == 0) { - // There are no stretchable spaces, so we can't help the justification by adding any - // width. - return; + if (justificationMode == Layout.JUSTIFICATION_MODE_INTER_WORD) { + float width = Math.abs(measure(end, false, null, null, null)); + final int spaces = countStretchableSpaces(0, end); + if (spaces == 0) { + // There are no stretchable spaces, so we can't help the justification by adding any + // width. + return; + } + mAddedWordSpacingInPx = (justifyWidth - width) / spaces; + mAddedLetterSpacingInPx = 0; + } else { // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER + LineInfo lineInfo = new LineInfo(); + float width = Math.abs(measure(end, false, null, null, lineInfo)); + + int lettersCount = lineInfo.getClusterCount(); + if (lettersCount < 2) { + return; + } + mAddedLetterSpacingInPx = (justifyWidth - width) / (lettersCount - 1); + if (mAddedLetterSpacingInPx > 0.03) { + // If the letter spacing is more than 0.03em, the ligatures are automatically + // disabled, so re-calculate everything without ligatures. + final String oldFontFeatures = mPaint.getFontFeatureSettings(); + mPaint.setFontFeatureSettings(oldFontFeatures + ", \"liga\" off, \"cliga\" off"); + width = Math.abs(measure(end, false, null, null, lineInfo)); + lettersCount = lineInfo.getClusterCount(); + mAddedLetterSpacingInPx = (justifyWidth - width) / (lettersCount - 1); + mPaint.setFontFeatureSettings(oldFontFeatures); + } + mAddedWordSpacingInPx = 0; } - final float width = Math.abs(measure(end, false, null, null, null)); - mAddedWidthForJustify = (justifyWidth - width) / spaces; mIsJustifying = true; } @@ -529,6 +568,9 @@ public class TextLine { throw new IndexOutOfBoundsException( "offset(" + offset + ") should be less than line limit(" + mLen + ")"); } + if (lineInfo != null) { + lineInfo.setClusterCount(0); + } final int target = trailing ? offset - 1 : offset; if (target < 0) { return 0; @@ -1076,7 +1118,8 @@ public class TextLine { TextPaint wp = mWorkPaint; wp.set(mPaint); if (mIsJustifying) { - wp.setWordSpacing(mAddedWidthForJustify); + wp.setWordSpacing(mAddedWordSpacingInPx); + wp.setLetterSpacing(mAddedLetterSpacingInPx / wp.getTextSize()); // Convert to Em } int spanStart = runStart; @@ -1277,7 +1320,8 @@ public class TextLine { @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo, int runFlag) { if (mIsJustifying) { - wp.setWordSpacing(mAddedWidthForJustify); + wp.setWordSpacing(mAddedWordSpacingInPx); + wp.setLetterSpacing(mAddedLetterSpacingInPx / wp.getTextSize()); // Convert to Em } // Get metrics first (even for empty strings or "0" width runs) if (drawBounds != null && fmi == null) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e0bda9181380..3c36227eda0a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -26,7 +26,6 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; -import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -1012,10 +1011,8 @@ public final class ViewRootImpl implements ViewParent, // Used to check if there were any view invalidations in // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). private boolean mHasInvalidation = false; - // Used to check if it is in the frame rate boosting period. + // Used to check if it is in the touch boosting period. private boolean mIsFrameRateBoosting = false; - // Used to check if it is in touch boosting period. - private boolean mIsTouchBoosting = false; // Used to check if there is a message in the message queue // for idleness handling. private boolean mHasIdledMessage = false; @@ -6424,12 +6421,11 @@ public final class ViewRootImpl implements ViewParent, * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). */ mIsFrameRateBoosting = false; - mIsTouchBoosting = false; setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, mLastPreferredFrameRateCategory)); break; case MSG_CHECK_INVALIDATION_IDLE: - if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) { + if (!mHasInvalidation && !mIsFrameRateBoosting) { mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; setPreferredFrameRateCategory(mPreferredFrameRateCategory); mHasIdledMessage = false; @@ -7452,7 +7448,7 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { // set the frame rate to the maximum value. - mIsTouchBoosting = true; + mIsFrameRateBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); } /** @@ -12204,16 +12200,8 @@ public final class ViewRootImpl implements ViewParent, return; } - int frameRateCategory = mIsTouchBoosting - ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory; - - // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT - // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. - // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction - // (e.g., Window Initialization). - if (mIsFrameRateBoosting || mInsetsAnimationRunning) { - frameRateCategory = FRAME_RATE_CATEGORY_HIGH; - } + int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning + ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; try { if (mLastPreferredFrameRateCategory != frameRateCategory) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index c8fd246a255b..656cc3e2af71 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -188,8 +188,6 @@ cc_library_shared_for_libandroid_runtime { "android_os_SharedMemory.cpp", "android_os_storage_StorageManager.cpp", "android_os_UEventObserver.cpp", - "android_os_VintfObject.cpp", - "android_os_VintfRuntimeInfo.cpp", "android_os_incremental_IncrementalManager.cpp", "android_net_LocalSocketImpl.cpp", "android_service_DataLoaderService.cpp", @@ -280,6 +278,7 @@ cc_library_shared_for_libandroid_runtime { "libdmabufinfo", "libgif", "libgui_window_info_static", + "libkernelconfigs", "libseccomp_policy", "libgrallocusage", "libscrypt_static", @@ -350,7 +349,6 @@ cc_library_shared_for_libandroid_runtime { "libnativeloader_lazy", "libmemunreachable", "libhidlbase", - "libvintf", "libnativedisplay", "libnativewindow", "libdl", @@ -458,8 +456,25 @@ cc_library_shared_for_libandroid_runtime { // (e.g. gDefaultServiceManager) "libbinder", "libhidlbase", // libhwbinder is in here - "libvintf", ], }, }, } + +cc_library_shared { + name: "libvintf_jni", + + cpp_std: "gnu++20", + + srcs: [ + "android_os_VintfObject.cpp", + "android_os_VintfRuntimeInfo.cpp", + ], + + shared_libs: [ + "libbase", + "liblog", + "libnativehelper", + "libvintf", + ], +} diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 7a16318f3276..aa63f4fa03d4 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -152,8 +152,6 @@ extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_Parcel(JNIEnv* env); extern int register_android_os_PerformanceHintManager(JNIEnv* env); extern int register_android_os_SELinux(JNIEnv* env); -extern int register_android_os_VintfObject(JNIEnv *env); -extern int register_android_os_VintfRuntimeInfo(JNIEnv *env); extern int register_android_os_storage_StorageManager(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv *env); extern int register_android_os_SystemClock(JNIEnv* env); @@ -1545,8 +1543,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_NativeHandle), REG_JNI(register_android_os_ServiceManager), REG_JNI(register_android_os_storage_StorageManager), - REG_JNI(register_android_os_VintfObject), - REG_JNI(register_android_os_VintfRuntimeInfo), REG_JNI(register_android_service_DataLoaderService), REG_JNI(register_android_view_DisplayEventReceiver), REG_JNI(register_android_view_Surface), diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index de1ce4e29198..1504a00f972c 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -52,7 +52,7 @@ #include <memunreachable/memunreachable.h> #include <android-base/strings.h> #include "android_os_Debug.h" -#include <vintf/VintfObject.h> +#include <vintf/KernelConfigs.h> namespace android { @@ -1004,10 +1004,9 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz) } cfg_state = CONFIG_UNKNOWN; if (cfg_state == CONFIG_UNKNOWN) { - auto runtime_info = vintf::VintfObject::GetInstance()->getRuntimeInfo( - vintf::RuntimeInfo::FetchFlag::CONFIG_GZ); - CHECK(runtime_info != nullptr) << "Kernel configs cannot be fetched. b/151092221"; - const std::map<std::string, std::string>& configs = runtime_info->kernelConfigs(); + std::map<std::string, std::string> configs; + const status_t result = android::kernelconfigs::LoadKernelConfigs(&configs); + CHECK(result == OK) << "Kernel configs could not be fetched. b/151092221"; std::map<std::string, std::string>::const_iterator it = configs.find("CONFIG_VMAP_STACK"); cfg_state = (it != configs.end() && it->second == "y") ? CONFIG_SET : CONFIG_UNSET; } diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index 477bd096b11a..734b5f497e2e 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -39,7 +39,6 @@ #include <hwbinder/ProcessState.h> #include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedUtfChars.h> -#include <vintf/parse_string.h> #include <utils/misc.h> #include "core_jni_helpers.h" diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp index a5b2f65eafc7..ce4a33735c6d 100644 --- a/core/jni/android_os_VintfObject.cpp +++ b/core/jni/android_os_VintfObject.cpp @@ -17,16 +17,14 @@ #define LOG_TAG "VintfObject" //#define LOG_NDEBUG 0 #include <android-base/logging.h> - -#include <vector> -#include <string> - -#include <nativehelper/JNIHelp.h> #include <vintf/VintfObject.h> #include <vintf/parse_string.h> #include <vintf/parse_xml.h> -#include "core_jni_helpers.h" +#include <vector> +#include <string> + +#include "jni_wrappers.h" static jclass gString; static jclass gHashMapClazz; @@ -94,7 +92,7 @@ static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass) return toJavaStringArray(env, cStrings); } -static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) { +static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv*, jclass) { std::string error; // Use temporary VintfObject, not the shared instance, to release memory // after check. @@ -204,4 +202,23 @@ int register_android_os_VintfObject(JNIEnv* env) NELEM(gVintfObjectMethods)); } -}; +extern int register_android_os_VintfRuntimeInfo(JNIEnv* env); + +} // namespace android + +jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + JNIEnv* env = NULL; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) { + return JNI_ERR; + } + + if (android::register_android_os_VintfObject(env) < 0) { + return JNI_ERR; + } + + if (android::register_android_os_VintfRuntimeInfo(env) < 0) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp index b0271b9e92af..7c2f58829446 100644 --- a/core/jni/android_os_VintfRuntimeInfo.cpp +++ b/core/jni/android_os_VintfRuntimeInfo.cpp @@ -17,23 +17,22 @@ #define LOG_TAG "VintfRuntimeInfo" //#define LOG_NDEBUG 0 -#include <nativehelper/JNIHelp.h> #include <vintf/VintfObject.h> #include <vintf/parse_string.h> #include <vintf/parse_xml.h> -#include "core_jni_helpers.h" +#include "jni_wrappers.h" namespace android { using vintf::RuntimeInfo; using vintf::VintfObject; -#define MAP_STRING_METHOD(javaMethod, cppString, flags) \ - static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) { \ - std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \ - if (info == nullptr) return nullptr; \ - return env->NewStringUTF((cppString).c_str()); \ +#define MAP_STRING_METHOD(javaMethod, cppString, flags) \ + static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass) { \ + std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \ + if (info == nullptr) return nullptr; \ + return env->NewStringUTF((cppString).c_str()); \ } MAP_STRING_METHOD(getCpuInfo, info->cpuInfo(), RuntimeInfo::FetchFlag::CPU_INFO); @@ -49,9 +48,7 @@ MAP_STRING_METHOD(getBootAvbVersion, vintf::to_string(info->bootAvbVersion()), MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()), RuntimeInfo::FetchFlag::AVB); - -static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz) -{ +static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv*, jclass) { std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(RuntimeInfo::FetchFlag::POLICYVERS); if (info == nullptr) return 0; diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h index 210dc895d674..769fa723c96e 100644 --- a/core/jni/core_jni_helpers.h +++ b/core/jni/core_jni_helpers.h @@ -22,6 +22,8 @@ #include <nativehelper/scoped_utf_chars.h> #include <android_runtime/AndroidRuntime.h> +#include "jni_wrappers.h" + // Host targets (layoutlib) do not differentiate between regular and critical native methods, // and they need all the JNI methods to have JNIEnv* and jclass/jobject as their first two arguments. // The following macro allows to have those arguments when compiling for host while omitting them when @@ -36,60 +38,6 @@ namespace android { -// Defines some helpful functions. - -static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { - jclass clazz = env->FindClass(class_name); - LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name); - return clazz; -} - -static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name, - const char* field_signature) { - jfieldID res = env->GetFieldID(clazz, field_name, field_signature); - LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name, - field_signature); - return res; -} - -static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, - const char* method_signature) { - jmethodID res = env->GetMethodID(clazz, method_name, method_signature); - LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name, - method_signature); - return res; -} - -static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name, - const char* field_signature) { - jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature); - LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name, - field_signature); - return res; -} - -static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, - const char* method_signature) { - jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature); - LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s", - method_name, method_signature); - return res; -} - -template <typename T> -static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { - jobject res = env->NewGlobalRef(in); - LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference."); - return static_cast<T>(res); -} - -static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, - const JNINativeMethod* gMethods, int numMethods) { - int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods); - LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); - return res; -} - /** * Returns the result of invoking java.lang.ref.Reference.get() on a Reference object. */ diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h new file mode 100644 index 000000000000..3b29e305e410 --- /dev/null +++ b/core/jni/jni_wrappers.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// JNI wrappers for better logging + +#include <jni.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> + +namespace android { + +static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name); + return clazz; +} + +static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name, + const char* field_signature) { + jfieldID res = env->GetFieldID(clazz, field_name, field_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name, + field_signature); + return res; +} + +static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, + const char* method_signature) { + jmethodID res = env->GetMethodID(clazz, method_name, method_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name, + method_signature); + return res; +} + +static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name, + const char* field_signature) { + jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name, + field_signature); + return res; +} + +static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, + const char* method_signature) { + jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s", + method_name, method_signature); + return res; +} + +template <typename T> +static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { + jobject res = env->NewGlobalRef(in); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference."); + return static_cast<T>(res); +} + +static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, + const JNINativeMethod* gMethods, int numMethods) { + int res = jniRegisterNativeMethods(env, className, gMethods, numMethods); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + return res; +} + +} // namespace android diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b199420537d5..52cf67981bb3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3569,6 +3569,13 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION" android:protectionLevel="internal|role" /> + <!-- Allows an application to set policy related to <a + href="https://www.threadgroup.org">Thread</a> network. + @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK" + android:protectionLevel="internal|role" /> + <!-- Allows an application to set policy related to windows. <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call APIs protected by this permission on users different to the calling user. @@ -4961,7 +4968,7 @@ <p>Protection level: signature --> <permission android:name="android.permission.BIND_NFC_SERVICE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|module" /> <!-- Must be required by a {@link android.service.quickaccesswallet.QuickAccessWalletService} to ensure that only the system can bind to it. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5be29a6d68b8..9e1400641084 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -57,6 +57,7 @@ <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item> <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item> <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_oem_satellite</xliff:g></item> <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item> <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item> <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item> @@ -102,6 +103,7 @@ <string translatable="false" name="status_bar_call_strength">call_strength</string> <string translatable="false" name="status_bar_sensors_off">sensors_off</string> <string translatable="false" name="status_bar_screen_record">screen_record</string> + <string translatable="false" name="status_bar_oem_satellite">satellite</string> <!-- Flag indicating whether the surface flinger has limited alpha compositing functionality in hardware. If set, the window @@ -5334,20 +5336,19 @@ and a second time clipped to the fill level to indicate charge --> <bool name="config_batterymeterDualTone">false</bool> - <!-- The default refresh rate for a given device. Change this value to set a higher default - refresh rate. If the hardware composer on the device supports display modes with a higher - refresh rate than the default value specified here, the framework may use those higher - refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling - setFrameRate(). - If a non-zero value is set for config_defaultPeakRefreshRate, then - config_defaultRefreshRate may be set to 0, in which case the value set for - config_defaultPeakRefreshRate will act as the default frame rate. --> - <integer name="config_defaultRefreshRate">60</integer> - - <!-- The default peak refresh rate for a given device. Change this value if you want to prevent - the framework from using higher refresh rates, even if display modes with higher refresh - rates are available from hardware composer. Only has an effect if the value is - non-zero. --> + <!-- The default refresh rate for a given device. This value is used to set the + global refresh rate vote, and when set to zero it has no effect on the vote. + If this value is non-zero but the hardware composer on the device supports + display modes with higher refresh rates, the framework may use those higher + refresh rate modes if an app chooses one by setting preferredDisplayModeId + or calling setFrameRate().--> + <integer name="config_defaultRefreshRate">0</integer> + + <!-- The default peak refresh rate for a given device. This value is used to set the + global peak refresh rate vote, and when set to zero it has no effect on the vote. + Change this value to non-zero if you want to prevent the framework from using higher + refresh rates, even if display modes with higher refresh rates are available from + hardware composer. --> <integer name="config_defaultPeakRefreshRate">0</integer> <!-- External display peak refresh rate for the given device. Change this value if you want to diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d12ef2b95f06..ef12d8f4ac0f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3198,6 +3198,7 @@ <java-symbol type="string" name="status_bar_camera" /> <java-symbol type="string" name="status_bar_sensors_off" /> <java-symbol type="string" name="status_bar_screen_record" /> + <java-symbol type="string" name="status_bar_oem_satellite" /> <!-- Locale picker --> <java-symbol type="id" name="locale_search_menu" /> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java index bbac69f35e81..4f9b2697ee89 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -728,10 +729,10 @@ public final class RadioManagerTest { @Test public void equals_withFmBandConfigsOfDifferentAfSupportValues() { - RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig( - new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, - FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, - !AF_SUPPORTED, EA_SUPPORTED)); + RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder( + createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED) + .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED); + RadioManager.FmBandConfig fmBandConfigCompared = builder.build(); assertWithMessage("FM Band Config of different af support value") .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared); @@ -1300,6 +1301,18 @@ public final class RadioManagerTest { } @Test + public void addAnnouncementListener_withListenerAddedBeforeAndCloseException_throws() + throws Exception { + createRadioManager(); + Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE); + mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener); + doThrow(new RemoteException()).when(mCloseHandleMock).close(); + + assertThrows(RuntimeException.class, + () -> mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener)); + } + + @Test public void addAnnouncementListener_whenServiceDied_throwException() throws Exception { createRadioManager(); String exceptionMessage = "service is dead"; diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index 4841711f712d..4cda26de2906 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -125,6 +125,16 @@ public final class TunerAdapterTest { } @Test + public void close_forTunerAdapterCalledTwice() throws Exception { + mRadioTuner.close(); + verify(mTunerMock).close(); + + mRadioTuner.close(); + + verify(mTunerMock).close(); + } + + @Test public void setConfiguration_forTunerAdapter() throws Exception { int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG); @@ -134,6 +144,12 @@ public final class TunerAdapterTest { } @Test + public void setConfiguration_withNull_fails() throws Exception { + assertWithMessage("Status for setting configuration with null") + .that(mRadioTuner.setConfiguration(null)).isEqualTo(RadioManager.STATUS_BAD_VALUE); + } + + @Test public void setConfiguration_withInvalidParameters_fails() throws Exception { doThrow(new IllegalArgumentException()).when(mTunerMock).setConfiguration(any()); @@ -840,6 +856,15 @@ public final class TunerAdapterTest { } @Test + public void onTuneFailed_withDeadService() throws Exception { + mTunerCallback.onTuneFailed(RadioManager.STATUS_DEAD_OBJECT, FM_SELECTOR); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed( + RadioManager.STATUS_DEAD_OBJECT, FM_SELECTOR); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SERVER_DIED); + } + + @Test public void onProgramListChanged_forTunerCallbackAdapter() throws Exception { mTunerCallback.onProgramListChanged(); diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index e118c98dd4da..3ee565f8e025 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -403,4 +403,41 @@ public class SQLiteDatabaseTest { } assertFalse(allowed); } + + /** Return true if the path is in the list of strings. */ + private boolean isConcurrent(String path) throws Exception { + path = new File(path).toPath().toRealPath().toString(); + return SQLiteDatabase.getConcurrentDatabasePaths().contains(path); + } + + @Test + public void testDuplicateDatabases() throws Exception { + // The two database paths in this test are assumed not to have been opened earlier in this + // process. + + // A database path that will be opened twice. + final String dbName = "never-used-db.db"; + final File dbFile = mContext.getDatabasePath(dbName); + final String dbPath = dbFile.getPath(); + + // A database path that will be opened only once. + final String okName = "never-used-ok.db"; + final File okFile = mContext.getDatabasePath(okName); + final String okPath = okFile.getPath(); + + SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null); + assertFalse(isConcurrent(dbPath)); + SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null); + assertTrue(isConcurrent(dbPath)); + db1.close(); + assertTrue(isConcurrent(dbPath)); + db2.close(); + assertTrue(isConcurrent(dbPath)); + + SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null); + db3.close(); + db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null); + assertFalse(isConcurrent(okPath)); + db3.close(); + } } diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt new file mode 100644 index 000000000000..a52561557523 --- /dev/null +++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 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.text + +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TextLineJustificationTest { + + @Rule + @JvmField + val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + private val PAINT = TextPaint().apply { + textSize = 10f // make 1em = 10px + } + + private fun makeTextLine(cs: CharSequence, paint: TextPaint) = TextLine.obtain().apply { + set(paint, cs, 0, cs.length, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, + null, 0, 0, false) + } + + private fun getClusterCount(cs: CharSequence, paint: TextPaint) = TextLine.LineInfo().apply { + makeTextLine(cs, paint).also { + it.metrics(null, null, false, this) + TextLine.recycle(it) + } + }.clusterCount + + fun justifyTest_WithoutJustify() { + val line = "Hello, World." + val tl = makeTextLine(line, PAINT) + + // Without calling justify method, justifying should be false and all added spaces should + // be zeros. + assertThat(tl.isJustifying).isFalse() + assertThat(tl.addedWordSpacingInPx).isEqualTo(0) + assertThat(tl.addedLetterSpacingInPx).isEqualTo(0) + } + + @Test + fun justifyTest_IntrCharacter_Latin() { + val line = "Hello, World." + val clusterCount = getClusterCount(line, PAINT) + val originalWidth = Layout.getDesiredWidth(line, PAINT) + val extraWidth = 100f + + val tl = makeTextLine(line, PAINT) + tl.justify(Layout.JUSTIFICATION_MODE_INTER_CHARACTER, originalWidth + extraWidth) + + assertThat(tl.isJustifying).isTrue() + assertThat(tl.addedWordSpacingInPx).isEqualTo(0) + assertThat(tl.addedLetterSpacingInPx).isEqualTo(extraWidth / (clusterCount - 1)) + + TextLine.recycle(tl) + } + + @Test + fun justifyTest_IntrCharacter_Japanese() { + val line = "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002" + val clusterCount = getClusterCount(line, PAINT) + val originalWidth = Layout.getDesiredWidth(line, PAINT) + val extraWidth = 100f + + val tl = makeTextLine(line, PAINT) + tl.justify(Layout.JUSTIFICATION_MODE_INTER_CHARACTER, originalWidth + extraWidth) + + assertThat(tl.isJustifying).isTrue() + assertThat(tl.addedWordSpacingInPx).isEqualTo(0) + assertThat(tl.addedLetterSpacingInPx).isEqualTo(extraWidth / (clusterCount - 1)) + + TextLine.recycle(tl) + } + + @Test + fun justifyTest_IntrWord_Latin() { + val line = "Hello, World." + val originalWidth = Layout.getDesiredWidth(line, PAINT) + val extraWidth = 100f + + val tl = makeTextLine(line, PAINT) + tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, originalWidth + extraWidth) + + assertThat(tl.isJustifying).isTrue() + // This text contains only one whitespace, so word spacing should be same to the extraWidth. + assertThat(tl.addedWordSpacingInPx).isEqualTo(extraWidth) + assertThat(tl.addedLetterSpacingInPx).isEqualTo(0) + + TextLine.recycle(tl) + } + + @Test + fun justifyTest_IntrWord_Japanese() { + val line = "\u672C\u65E5\u306F\u6674\u0020\u5929\u306A\u308A\u3002" + val originalWidth = Layout.getDesiredWidth(line, PAINT) + val extraWidth = 100f + + val tl = makeTextLine(line, PAINT) + tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, originalWidth + extraWidth) + + assertThat(tl.isJustifying).isTrue() + // This text contains only one whitespace, so word spacing should be same to the extraWidth. + assertThat(tl.addedWordSpacingInPx).isEqualTo(extraWidth) + assertThat(tl.addedLetterSpacingInPx).isEqualTo(0) + + TextLine.recycle(tl) + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java index a31992c8cfa1..8ae5669de55f 100644 --- a/core/tests/coretests/src/android/text/TextLineTest.java +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -53,7 +53,7 @@ public class TextLineTest { final float originalWidth = tl.metrics(null, null, false, null); final float expandedWidth = 2 * originalWidth; - tl.justify(expandedWidth); + tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, expandedWidth); final float newWidth = tl.metrics(null, null, false, null); TextLine.recycle(tl); return Math.abs(newWidth - expandedWidth) < 0.5; diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index cfbda84f24e1..cf3eb12498ca 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -19,7 +19,6 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; -import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; @@ -576,13 +575,8 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_HIGH_HINT); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 607b4bdf71ca..294b8ae0f6f5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -23,40 +23,48 @@ applications that come with the platform <!-- Needed for Build.getSerial(), which is used to send a unique number for serial, per HUIG. --> <privapp-permissions package="android.car.usb.handler"> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.angle"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.apps.tag"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.backupconfirm"> <permission name="android.permission.BACKUP"/> <permission name="android.permission.CRYPT_KEEPER"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.externalstorage"> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.imsserviceentitlement"> <permission name="android.permission.MODIFY_PHONE_STATE" /> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.launcher3"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.location.fused"> <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.managedprovisioning"> @@ -79,12 +87,14 @@ applications that come with the platform <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.mms.service"> <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/> <permission name="android.permission.BIND_CARRIER_SERVICES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.mtp"> @@ -94,16 +104,19 @@ applications that come with the platform <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.musicfx"> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.networkrecommendation"> <permission name="android.permission.SCORE_NETWORKS"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.packageinstaller"> @@ -114,6 +127,7 @@ applications that come with the platform <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.phone"> @@ -170,6 +184,7 @@ applications that come with the platform <permission name="android.permission.LOG_COMPAT_CHANGE"/> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <permission name="android.permission.UWB_PRIVILEGED"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.calendar"> @@ -180,6 +195,7 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.LOG_COMPAT_CHANGE" /> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.contacts"> @@ -193,6 +209,7 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.LOG_COMPAT_CHANGE" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.downloads"> @@ -205,6 +222,7 @@ applications that come with the platform <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.providers.telephony"> @@ -214,6 +232,7 @@ applications that come with the platform <!-- Permissions required for reading and logging compat changes --> <permission name="android.permission.LOG_COMPAT_CHANGE" /> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.server.telecom"> @@ -229,11 +248,13 @@ applications that come with the platform <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.STOP_APP_SWITCHES"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.sharedstoragebackup"> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.shell"> @@ -547,16 +568,19 @@ applications that come with the platform <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> <!-- Permission required for BinaryTransparencyService shell API and host test --> <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/> <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.soundpicker"> <permission name="android.permission.INTERACT_ACROSS_USERS" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.tv"> @@ -568,15 +592,18 @@ applications that come with the platform <permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/> <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/> <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.vpndialogs"> <permission name="android.permission.CONTROL_VPN"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.wallpaper.livepicker"> <permission name="android.permission.SET_WALLPAPER_COMPONENT"/> <permission name="android.permission.BIND_WALLPAPER"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.wallpaper"> @@ -584,25 +611,30 @@ applications that come with the platform <permission name="android.permission.BIND_WALLPAPER"/> <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/> <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.dynsystem"> <permission name="android.permission.REBOOT"/> <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/> <permission name="android.permission.READ_OEM_UNLOCK_STATE"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.settings"> <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/> <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" /> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.bips"> <permission name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> <privapp-permissions package="com.android.calllogbackup"> <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> + <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> </permissions> diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index 0e3fb163ef75..97071260402c 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -17,6 +17,7 @@ package android.graphics.text; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; +import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION; import android.annotation.FlaggedApi; import android.annotation.FloatRange; @@ -163,7 +164,8 @@ public class LineBreaker { /** @hide */ @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = { JUSTIFICATION_MODE_NONE, - JUSTIFICATION_MODE_INTER_WORD + JUSTIFICATION_MODE_INTER_WORD, + JUSTIFICATION_MODE_INTER_CHARACTER, }) @Retention(RetentionPolicy.SOURCE) public @interface JustificationMode {} @@ -179,6 +181,12 @@ public class LineBreaker { public static final int JUSTIFICATION_MODE_INTER_WORD = 1; /** + * Value for justification mode indicating the text is justified by stretching letter spacing. + */ + @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION) + public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; + + /** * Helper class for creating a {@link LineBreaker}. */ public static final class Builder { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java index 30d5edb59c85..160f922dd928 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java @@ -199,6 +199,10 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { } private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) { + if (leash == null || !leash.isValid()) { + return; + } + final float scale = targetRect.width() / mStartTaskRect.width(); mTransformMatrix.reset(); mTransformMatrix.setScale(scale, scale); @@ -211,12 +215,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { private void finishAnimation() { if (mEnteringTarget != null) { - mTransaction.setCornerRadius(mEnteringTarget.leash, 0); - mEnteringTarget.leash.release(); + if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) { + mTransaction.setCornerRadius(mEnteringTarget.leash, 0); + mEnteringTarget.leash.release(); + } mEnteringTarget = null; } if (mClosingTarget != null) { - mClosingTarget.leash.release(); + if (mClosingTarget.leash != null) { + mClosingTarget.leash.release(); + } mClosingTarget = null; } if (mBackground != null) { @@ -260,7 +268,9 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { } private void onGestureCommitted() { - if (mEnteringTarget == null || mClosingTarget == null) { + if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null + || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid() + || !mClosingTarget.leash.isValid()) { finishAnimation(); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index ac2a1c867462..adc78391f033 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -208,7 +208,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { float top = mapRange(progress, mClosingStartRect.top, targetTop); float width = mapRange(progress, mClosingStartRect.width(), targetWidth); float height = mapRange(progress, mClosingStartRect.height(), targetHeight); - mTransaction.setLayer(mClosingTarget.leash, 0); + if (mClosingTarget.leash != null && mClosingTarget.leash.isValid()) { + mTransaction.setLayer(mClosingTarget.leash, 0); + } mClosingCurrentRect.set(left, top, left + width, top + height); applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius); @@ -226,7 +228,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { /** Transform the target window to match the target rect. */ private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) { - if (leash == null) { + if (leash == null || !leash.isValid()) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index d4a92894a397..a5f7880cfd41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -137,7 +137,7 @@ import java.util.function.IntConsumer; * The controller manages addition, removal, and visible state of bubbles on screen. */ public class BubbleController implements ConfigurationChangeListener, - RemoteCallable<BubbleController> { + RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; @@ -706,6 +706,7 @@ public class BubbleController implements ConfigurationChangeListener, return mBubbleIconFactory; } + @Override public Bubbles.SysuiProxy getSysuiProxy() { return mSysuiProxy; } @@ -732,8 +733,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView == null) { mStackView = new BubbleStackView( mContext, this, mBubbleData, mSurfaceSynchronizer, - mFloatingContentCoordinator, - mMainExecutor); + mFloatingContentCoordinator, this, mMainExecutor); mStackView.onOrientationChanged(); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index c25d41275f2b..a619401301aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -206,6 +206,7 @@ public class BubbleStackView extends FrameLayout }; private final BubbleController mBubbleController; private final BubbleData mBubbleData; + private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider; private StackViewState mStackViewState = new StackViewState(); private final ValueAnimator mDismissBubbleAnimator; @@ -875,12 +876,14 @@ public class BubbleStackView extends FrameLayout public BubbleStackView(Context context, BubbleController bubbleController, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, + Bubbles.SysuiProxy.Provider sysuiProxyProvider, ShellExecutor mainExecutor) { super(context); mMainExecutor = mainExecutor; mBubbleController = bubbleController; mBubbleData = data; + mSysuiProxyProvider = sysuiProxyProvider; Resources res = getResources(); mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); @@ -2090,7 +2093,7 @@ public class BubbleStackView extends FrameLayout hideCurrentInputMethod(); - mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand); + mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand); if (wasExpanded) { stopMonitoringSwipeUpGesture(); @@ -3034,7 +3037,7 @@ public class BubbleStackView extends FrameLayout if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { mManageMenu.setVisibility(View.INVISIBLE); mManageMenuScrim.setVisibility(INVISIBLE); - mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */); + mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */); return; } if (show) { @@ -3048,7 +3051,7 @@ public class BubbleStackView extends FrameLayout } }; - mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show); + mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show); mManageMenuScrim.animate() .setInterpolator(show ? ALPHA_IN : ALPHA_OUT) .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 759246eb285d..28af0ca6ac6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -321,6 +321,13 @@ public interface Bubbles { /** Callback to tell SysUi components execute some methods. */ interface SysuiProxy { + + /** Provider interface for {@link SysuiProxy}. */ + interface Provider { + /** Returns {@link SysuiProxy}. */ + SysuiProxy getSysuiProxy(); + } + void isNotificationPanelExpand(Consumer<Boolean> callback); void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index 9ce46d69815b..e9cd73b0df5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -95,8 +95,8 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " - + "entering PIP from an Activity Embedding window"); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for entering PIP from" + + " an Activity Embedding window #%d", info.getDebugId()); // Split into two transitions (wct) TransitionInfo.Change pipChange = null; final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); @@ -146,6 +146,8 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent" + + " with a remote transition and PIP #%d", info.getDebugId()); boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip( info, startTransaction, finishTransaction, finishCallback); // Consume the transition on remote handler if the leftover handler already handle this @@ -192,8 +194,9 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { } return false; } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" - + " animation because remote-animation likely doesn't support it"); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate" + + " animation because remote-animation likely doesn't support it #%d", + info.getDebugId()); // Split the transition into 2 parts: the pip part and the rest. mInFlightSubAnimations = 2; // make a new startTransaction because pip's startEnterAnimation "consumes" it so @@ -218,6 +221,9 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for unfolding #%d", + info.getDebugId()); + final Transitions.TransitionFinishCallback finishCB = (wct) -> { mInFlightSubAnimations--; if (mInFlightSubAnimations > 0) return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 643e0266d7df..4ea71490798c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -30,9 +30,11 @@ import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.StageCoordinator; @@ -77,6 +79,9 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition for Recents during" + + " Desktop #%d", info.getDebugId()); + if (mInfo == null) { mInfo = info; mFinishT = finishTransaction; @@ -109,6 +114,9 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during" + + " Keyguard #%d", info.getDebugId()); + if (mInfo == null) { mInfo = info; mFinishT = finishTransaction; @@ -122,6 +130,9 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during" + + " split screen #%d", info.getDebugId()); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); // Pip auto-entering info might be appended to recent transition like pressing diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index f58332198696..4878df806f82 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -125,7 +125,7 @@ class BubbleViewInfoTest : ShellTestCase() { mock<BubbleProperties>()) bubbleStackView = BubbleStackView(context, bubbleController, bubbleData, - surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor) + surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor) bubbleBarLayerView = BubbleBarLayerView(context, bubbleController) } diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp index 4d020c567972..5f5ffe97e953 100644 --- a/libs/hwui/AutoBackendTextureRelease.cpp +++ b/libs/hwui/AutoBackendTextureRelease.cpp @@ -21,6 +21,7 @@ #include <include/gpu/GrDirectContext.h> #include <include/gpu/GrBackendSurface.h> #include <include/gpu/MutableTextureState.h> +#include <include/gpu/vk/VulkanMutableTextureState.h> #include "renderthread/RenderThread.h" #include "utils/Color.h" #include "utils/PaintUtils.h" @@ -142,8 +143,9 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context) LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan); if (mBackendTexture.isValid()) { // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout. - skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED, - VK_QUEUE_FAMILY_FOREIGN_EXT); + skgpu::MutableTextureState newState = skgpu::MutableTextureStates::MakeVulkan( + VK_IMAGE_LAYOUT_UNDEFINED, + VK_QUEUE_FAMILY_FOREIGN_EXT); // The unref for this ref happens in the releaseProc passed into setBackendTextureState. The // releaseProc callback will be made when the work to set the new state has finished on the diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp index acf893e9544c..79acb6cc35e5 100644 --- a/libs/hwui/jni/PathMeasure.cpp +++ b/libs/hwui/jni/PathMeasure.cpp @@ -17,7 +17,11 @@ #include "GraphicsJNI.h" +#include "SkMatrix.h" +#include "SkPath.h" #include "SkPathMeasure.h" +#include "SkPoint.h" +#include "SkScalar.h" /* We declare an explicit pair, so that we don't have to rely on the java client to be sure not to edit the path while we have an active measure diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig index 5f1279ecceea..8c7c8716b2dc 100644 --- a/location/java/android/location/flags/gnss.aconfig +++ b/location/java/android/location/flags/gnss.aconfig @@ -36,15 +36,15 @@ flag { } flag { - name: "gnss_configuration_from_resource" + name: "replace_future_elapsed_realtime_jni" namespace: "location" - description: "Flag for GNSS configuration from resource" - bug: "317734846" + description: "Flag for replacing future elapsedRealtime in JNI" + bug: "314328533" } flag { - name: "replace_future_elapsed_realtime_jni" + name: "gnss_configuration_from_resource" namespace: "location" - description: "Flag for replacing future elapsedRealtime in JNI" - bug: "314328533" + description: "Flag for GNSS configuration from resource" + bug: "317734846" } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 2eec9b3d4a09..8dfa6be0fd11 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -754,15 +754,16 @@ interface IAudioService { void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher); - oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet); + oneway void startLoudnessCodecUpdates(int sessionId); - oneway void stopLoudnessCodecUpdates(int piid); + oneway void stopLoudnessCodecUpdates(int sessionId); - oneway void addLoudnessCodecInfo(int piid, int mediaCodecHash, in LoudnessCodecInfo codecInfo); + oneway void addLoudnessCodecInfo(int sessionId, int mediaCodecHash, + in LoudnessCodecInfo codecInfo); - oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); + oneway void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo); - PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo); + PersistableBundle getLoudnessParams(in LoudnessCodecInfo codecInfo); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") diff --git a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl index 16eaaea38b7f..202242755a97 100644 --- a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl +++ b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl @@ -16,6 +16,7 @@ package android.media; +import android.media.AudioAttributes; import android.os.PersistableBundle; /** @@ -26,6 +27,6 @@ import android.os.PersistableBundle; */ oneway interface ILoudnessCodecUpdatesDispatcher { - void dispatchLoudnessCodecParameterChange(int piid, in PersistableBundle params); + void dispatchLoudnessCodecParameterChange(int sessionId, in PersistableBundle params); }
\ No newline at end of file diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecController.java index aadd78328d68..b3e5c52b27b3 100644 --- a/media/java/android/media/LoudnessCodecConfigurator.java +++ b/media/java/android/media/LoudnessCodecController.java @@ -16,13 +16,13 @@ package android.media; -import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.media.permission.SafeCloseable; import android.os.Bundle; import android.util.Log; @@ -32,7 +32,6 @@ import androidx.annotation.Nullable; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -41,16 +40,21 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; /** - * Class for getting recommended loudness parameter updates for audio decoders, according to the - * encoded format and current audio routing. Those updates can be automatically applied to the - * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management - * parameter updates are defined by the CTA-2075 standard. - * <p>A new object should be instantiated for each {@link AudioTrack} with the help - * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}. + * Class for getting recommended loudness parameter updates for audio decoders as they are used + * to play back media content according to the encoded format and current audio routing. These + * audio decoder updates leverage loudness metadata present in compressed audio streams. They + * ensure the loudness and dynamic range of the content is optimized to the physical + * characteristics of the audio output device (e.g. phone microspeakers vs headphones vs TV + * speakers).Those updates can be automatically applied to the {@link MediaCodec} instance(s), or + * be provided to the user. The codec loudness management parameter updates are computed in + * accordance to the CTA-2075 standard. + * <p>A new object should be instantiated for each audio session + * (see {@link AudioManager#generateAudioSessionId()}) using creator methods {@link #create(int)} or + * {@link #create(int, Executor, OnLoudnessCodecUpdateListener)}. */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) -public class LoudnessCodecConfigurator { - private static final String TAG = "LoudnessCodecConfigurator"; +public class LoudnessCodecController implements SafeCloseable { + private static final String TAG = "LoudnessCodecController"; /** * Listener used for receiving asynchronous loudness metadata updates. @@ -67,6 +71,7 @@ public class LoudnessCodecConfigurator { * directly on the mediaCodec. The listener can modify * these values with their own edits which will be * returned for the mediaCodec configuration + * * @return a Bundle which contains the original computed codecValues * aggregated with user edits. The platform will configure the associated * MediaCodecs with the returned Bundle params. @@ -74,159 +79,116 @@ public class LoudnessCodecConfigurator { @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec, - @NonNull Bundle codecValues) { + @NonNull Bundle codecValues) { return codecValues; } } - @NonNull private final LoudnessCodecDispatcher mLcDispatcher; - - private final Object mConfiguratorLock = new Object(); - - @GuardedBy("mConfiguratorLock") - private AudioTrack mAudioTrack; + @NonNull + private final LoudnessCodecDispatcher mLcDispatcher; - @GuardedBy("mConfiguratorLock") - private final Executor mExecutor; + private final Object mControllerLock = new Object(); - @GuardedBy("mConfiguratorLock") - private final OnLoudnessCodecUpdateListener mListener; + private final int mSessionId; - @GuardedBy("mConfiguratorLock") + @GuardedBy("mControllerLock") private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>(); /** - * Creates a new instance of {@link LoudnessCodecConfigurator} + * Creates a new instance of {@link LoudnessCodecController} * * <p>This method should be used when the client does not need to alter the * codec loudness parameters before they are applied to the audio decoders. - * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}. + * Otherwise, use {@link #create(int, Executor, OnLoudnessCodecUpdateListener)}. * - * @return the {@link LoudnessCodecConfigurator} instance + * @param sessionId the session ID of the track that will receive data + * from the added {@link MediaCodec}'s + * + * @return the {@link LoudnessCodecController} instance */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public static @NonNull LoudnessCodecConfigurator create() { - return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), - Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); + public static @NonNull LoudnessCodecController create(int sessionId) { + final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher( + AudioManager.getService()); + final LoudnessCodecController controller = new LoudnessCodecController(dispatcher, + sessionId); + dispatcher.addLoudnessCodecListener(controller, Executors.newSingleThreadExecutor(), + new OnLoudnessCodecUpdateListener() {}); + dispatcher.startLoudnessCodecUpdates(sessionId); + return controller; } /** - * Creates a new instance of {@link LoudnessCodecConfigurator} + * Creates a new instance of {@link LoudnessCodecController} * * <p>This method should be used when the client wants to alter the codec * loudness parameters before they are applied to the audio decoders. - * Otherwise, use {@link #create()}. + * Otherwise, use {@link #create( int)}. * - * @param executor {@link Executor} to handle the callbacks - * @param listener used for receiving updates + * @param sessionId the session ID of the track that will receive data + * from the added {@link MediaCodec}'s + * @param executor {@link Executor} to handle the callbacks + * @param listener used for receiving updates * - * @return the {@link LoudnessCodecConfigurator} instance + * @return the {@link LoudnessCodecController} instance */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public static @NonNull LoudnessCodecConfigurator create( + public static @NonNull LoudnessCodecController create( + int sessionId, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); - return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), - executor, listener); + final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher( + AudioManager.getService()); + final LoudnessCodecController controller = new LoudnessCodecController(dispatcher, + sessionId); + dispatcher.addLoudnessCodecListener(controller, executor, listener); + dispatcher.startLoudnessCodecUpdates(sessionId); + return controller; } /** - * Creates a new instance of {@link LoudnessCodecConfigurator} + * Creates a new instance of {@link LoudnessCodecController} * * <p>This method should be used only in testing * - * @param service interface for communicating with AudioService + * @param sessionId the session ID of the track that will receive data + * from the added {@link MediaCodec}'s * @param executor {@link Executor} to handle the callbacks * @param listener used for receiving updates + * @param service interface for communicating with AudioService * - * @return the {@link LoudnessCodecConfigurator} instance - * + * @return the {@link LoudnessCodecController} instance * @hide */ - public static @NonNull LoudnessCodecConfigurator createForTesting( - @NonNull IAudioService service, + public static @NonNull LoudnessCodecController createForTesting( + int sessionId, @NonNull @CallbackExecutor Executor executor, - @NonNull OnLoudnessCodecUpdateListener listener) { + @NonNull OnLoudnessCodecUpdateListener listener, + @NonNull IAudioService service) { Objects.requireNonNull(service, "IAudioService cannot be null"); Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); - return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service), - executor, listener); + final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(service); + final LoudnessCodecController controller = new LoudnessCodecController(dispatcher, + sessionId); + dispatcher.addLoudnessCodecListener(controller, executor, listener); + dispatcher.startLoudnessCodecUpdates(sessionId); + return controller; } /** @hide */ - private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher, - @NonNull @CallbackExecutor Executor executor, - @NonNull OnLoudnessCodecUpdateListener listener) { + private LoudnessCodecController(@NonNull LoudnessCodecDispatcher lcDispatcher, int sessionId) { mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null"); - mExecutor = Objects.requireNonNull(executor, "Executor cannot be null"); - mListener = Objects.requireNonNull(listener, - "OnLoudnessCodecUpdateListener cannot be null"); - } - - /** - * Sets the {@link AudioTrack} and starts receiving asynchronous updates for - * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)}) - * - * <p>The AudioTrack should be the one that receives audio data from the - * added audio decoders and is used to determine the device routing on which - * the audio streaming will take place. This will directly influence the - * loudness parameters. - * <p>After calling this method the framework will compute the initial set of - * parameters which will be applied to the registered codecs/returned to the - * listener for modification. - * - * @param audioTrack the track that will receive audio data from the provided - * audio decoders. In case this is {@code null} this - * method will have the effect of clearing the existing set - * {@link AudioTrack} and will stop receiving asynchronous - * loudness updates - */ - @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setAudioTrack(@Nullable AudioTrack audioTrack) { - List<LoudnessCodecInfo> codecInfos; - int piid = PLAYER_PIID_INVALID; - int oldPiid = PLAYER_PIID_INVALID; - synchronized (mConfiguratorLock) { - if (mAudioTrack != null && mAudioTrack == audioTrack) { - Log.v(TAG, "Loudness configurator already started for piid: " - + mAudioTrack.getPlayerIId()); - return; - } - - codecInfos = getLoudnessCodecInfoList_l(); - if (mAudioTrack != null) { - oldPiid = mAudioTrack.getPlayerIId(); - mLcDispatcher.removeLoudnessCodecListener(this); - } - if (audioTrack != null) { - piid = audioTrack.getPlayerIId(); - mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener); - } - - mAudioTrack = audioTrack; - } - - if (oldPiid != PLAYER_PIID_INVALID) { - Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid); - mLcDispatcher.stopLoudnessCodecUpdates(oldPiid); - } - if (piid != PLAYER_PIID_INVALID) { - Log.v(TAG, "Loudness configurator starting updates for piid: " + piid); - mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos); - } + mSessionId = sessionId; } /** - * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack} - * which the client sets - * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}). - * - * <p>This method can be called while asynchronous updates are live. + * Adds a new {@link MediaCodec} that will stream data to a player + * which uses {@link #mSessionId}. * * <p>No new element will be added if the passed {@code mediaCodec} was * previously added. @@ -234,23 +196,22 @@ public class LoudnessCodecConfigurator { * @param mediaCodec the codec to start receiving asynchronous loudness * updates. The codec has to be in a configured or started * state in order to add it for loudness updates. + * @return {@code false} if the {@code mediaCodec} was not configured or does + * not contain loudness metadata, {@code true} otherwise. * @throws IllegalArgumentException if the same {@code mediaCodec} was already * added before. - * @return {@code false} if the {@code mediaCodec} was not configured or does - * not contain loudness metadata, {@code true} otherwise. */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) { final MediaCodec mc = Objects.requireNonNull(mediaCodec, "MediaCodec for addMediaCodec cannot be null"); - int piid = PLAYER_PIID_INVALID; final LoudnessCodecInfo mcInfo = getCodecInfo(mc); if (mcInfo == null) { Log.v(TAG, "Could not extract codec loudness information"); return false; } - synchronized (mConfiguratorLock) { + synchronized (mControllerLock) { final AtomicBoolean containsCodec = new AtomicBoolean(false); Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> { containsCodec.set(!codecSet.add(mc)); @@ -263,16 +224,12 @@ public class LoudnessCodecConfigurator { } if (containsCodec.get()) { throw new IllegalArgumentException( - "Loudness configurator already added " + mediaCodec); - } - if (mAudioTrack != null) { - piid = mAudioTrack.getPlayerIId(); + "Loudness controller already added " + mediaCodec); } } - if (piid != PLAYER_PIID_INVALID) { - mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo); - } + mLcDispatcher.addLoudnessCodecInfo(mSessionId, mediaCodec.hashCode(), + mcInfo); return true; } @@ -291,7 +248,6 @@ public class LoudnessCodecConfigurator { */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public void removeMediaCodec(@NonNull MediaCodec mediaCodec) { - int piid = PLAYER_PIID_INVALID; LoudnessCodecInfo mcInfo; AtomicBoolean removedMc = new AtomicBoolean(false); AtomicBoolean removeInfo = new AtomicBoolean(false); @@ -302,10 +258,7 @@ public class LoudnessCodecConfigurator { if (mcInfo == null) { throw new IllegalArgumentException("Could not extract codec loudness information"); } - synchronized (mConfiguratorLock) { - if (mAudioTrack != null) { - piid = mAudioTrack.getPlayerIId(); - } + synchronized (mControllerLock) { mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> { removedMc.set(mcs.remove(mediaCodec)); if (mcs.isEmpty()) { @@ -317,68 +270,81 @@ public class LoudnessCodecConfigurator { }); if (!removedMc.get()) { throw new IllegalArgumentException( - "Loudness configurator does not contain " + mediaCodec); + "Loudness controller does not contain " + mediaCodec); } } - if (piid != PLAYER_PIID_INVALID && removeInfo.get()) { - mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo); + if (removeInfo.get()) { + mLcDispatcher.removeLoudnessCodecInfo(mSessionId, mcInfo); } } /** - * Gets synchronous loudness updates when no listener is required. The provided - * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}. + * Returns the loudness parameters of the registered audio decoders + * + * <p>Those parameters may have been automatically applied if the + * {@code LoudnessCodecController} was created with {@link #create(int)}, or they are the + * parameters that have been sent to the {@link OnLoudnessCodecUpdateListener} if using a + * codec update listener. * - * @param audioTrack track that receives audio data from the passed - * {@link MediaCodec} - * @param mediaCodec codec that decodes loudness annotated data for the passed - * {@link AudioTrack} + * @param mediaCodec codec that decodes loudness annotated data. Has to be added + * with {@link #addMediaCodec(MediaCodec)} before calling this + * method + * @throws IllegalArgumentException if the passed {@link MediaCodec} was not + * added before calling this method * - * @return the {@link Bundle} containing the current loudness parameters. Caller is - * responsible to update the {@link MediaCodec} + * @return the {@link Bundle} containing the current loudness parameters. */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull - public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack, - @NonNull MediaCodec mediaCodec) { - Objects.requireNonNull(audioTrack, "Passed audio track cannot be null"); + public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) { + Objects.requireNonNull(mediaCodec, "MediaCodec cannot be null"); LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec); if (codecInfo == null) { - return new Bundle(); + throw new IllegalArgumentException("MediaCodec does not have valid codec information"); } - return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo); + synchronized (mControllerLock) { + final Set<MediaCodec> codecs = mMediaCodecs.get(codecInfo); + if (codecs == null || !codecs.contains(mediaCodec)) { + throw new IllegalArgumentException( + "MediaCodec was not added for loudness annotation"); + } + } + + return mLcDispatcher.getLoudnessCodecParams(codecInfo); } - /** @hide */ - /*package*/ int getAssignedTrackPiid() { - int piid = PLAYER_PIID_INVALID; + /** + * Stops any loudness updates and frees up the resources. + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public void release() { + close(); + } - synchronized (mConfiguratorLock) { - if (mAudioTrack == null) { - return piid; - } - piid = mAudioTrack.getPlayerIId(); + /** @hide */ + @Override + public void close() { + synchronized (mControllerLock) { + mMediaCodecs.clear(); } + mLcDispatcher.stopLoudnessCodecUpdates(mSessionId); + } - return piid; + /** @hide */ + /*package*/ int getSessionId() { + return mSessionId; } /** @hide */ /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() { - synchronized (mConfiguratorLock) { + synchronized (mControllerLock) { return mMediaCodecs; } } - @GuardedBy("mConfiguratorLock") - private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() { - return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map( - LoudnessCodecConfigurator::getCodecInfo)).toList(); - } - @Nullable private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) { LoudnessCodecInfo lci = new LoudnessCodecInfo(); diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java index b546a81b0498..46be54be55ec 100644 --- a/media/java/android/media/LoudnessCodecDispatcher.java +++ b/media/java/android/media/LoudnessCodecDispatcher.java @@ -21,7 +21,7 @@ import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; import android.annotation.CallbackExecutor; -import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener; +import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener; import android.os.Bundle; import android.os.PersistableBundle; import android.os.RemoteException; @@ -32,7 +32,6 @@ import androidx.annotation.NonNull; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -59,7 +58,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { private final Object mLock = new Object(); @GuardedBy("mLock") - private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> + private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecController> mConfiguratorListener = new HashMap<>(); public static synchronized LoudnessCodecUpdatesDispatcherStub getInstance() { @@ -72,16 +71,16 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { private LoudnessCodecUpdatesDispatcherStub() {} @Override - public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) { + public void dispatchLoudnessCodecParameterChange(int sessionId, PersistableBundle params) { if (DEBUG) { - Log.d(TAG, "dispatchLoudnessCodecParameterChange for piid " + piid + Log.d(TAG, "dispatchLoudnessCodecParameterChange for sessionId " + sessionId + " persistable bundle: " + params); } mLoudnessListenerMgr.callListeners(listener -> { synchronized (mLock) { mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> { // send the appropriate bundle for the user to update - if (lcConfig.getAssignedTrackPiid() == piid) { + if (lcConfig.getSessionId() == sessionId) { final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap = lcConfig.getRegisteredMediaCodecs(); for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) { @@ -111,7 +110,12 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { bundle)); if (!bundle.isDefinitelyEmpty()) { - mediaCodec.setParameters(bundle); + try { + mediaCodec.setParameters(bundle); + } catch (IllegalStateException e) { + Log.w(TAG, "Cannot set loudness bundle on media codec " + + mediaCodec); + } } if (canBreak) { break; @@ -145,7 +149,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { } void addLoudnessCodecListener(@NonNull CallbackUtil.DispatcherStub dispatcher, - @NonNull LoudnessCodecConfigurator configurator, + @NonNull LoudnessCodecController configurator, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { Objects.requireNonNull(configurator); @@ -160,15 +164,15 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { } } - void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) { + void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) { Objects.requireNonNull(configurator); OnLoudnessCodecUpdateListener listenerToRemove = null; synchronized (mLock) { - Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>> iterator = + Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController>> iterator = mConfiguratorListener.entrySet().iterator(); while (iterator.hasNext()) { - Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e = + Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController> e = iterator.next(); if (e.getValue() == configurator) { final OnLoudnessCodecUpdateListener listener = e.getKey(); @@ -208,7 +212,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { } /** @hide */ - public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator, + public void addLoudnessCodecListener(@NonNull LoudnessCodecController configurator, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { LoudnessCodecUpdatesDispatcherStub.getInstance().addLoudnessCodecListener(this, @@ -216,52 +220,52 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { } /** @hide */ - public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) { + public void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) { LoudnessCodecUpdatesDispatcherStub.getInstance().removeLoudnessCodecListener(configurator); } /** @hide */ - public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) { + public void startLoudnessCodecUpdates(int sessionId) { try { - mAudioService.startLoudnessCodecUpdates(piid, codecInfoList); + mAudioService.startLoudnessCodecUpdates(sessionId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ - public void stopLoudnessCodecUpdates(int piid) { + public void stopLoudnessCodecUpdates(int sessionId) { try { - mAudioService.stopLoudnessCodecUpdates(piid); + mAudioService.stopLoudnessCodecUpdates(sessionId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ - public void addLoudnessCodecInfo(int piid, int mediaCodecHash, + public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash, @NonNull LoudnessCodecInfo mcInfo) { try { - mAudioService.addLoudnessCodecInfo(piid, mediaCodecHash, mcInfo); + mAudioService.addLoudnessCodecInfo(sessionId, mediaCodecHash, mcInfo); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ - public void removeLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) { + public void removeLoudnessCodecInfo(int sessionId, @NonNull LoudnessCodecInfo mcInfo) { try { - mAudioService.removeLoudnessCodecInfo(piid, mcInfo); + mAudioService.removeLoudnessCodecInfo(sessionId, mcInfo); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ - public Bundle getLoudnessCodecParams(int piid, @NonNull LoudnessCodecInfo mcInfo) { + public Bundle getLoudnessCodecParams(@NonNull LoudnessCodecInfo mcInfo) { Bundle loudnessParams = null; try { - loudnessParams = new Bundle(mAudioService.getLoudnessParams(piid, mcInfo)); + loudnessParams = new Bundle(mAudioService.getLoudnessParams(mcInfo)); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 587e35b4b1fc..5e40eee26886 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -17,6 +17,7 @@ package android.media; import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE; +import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -121,6 +122,10 @@ import java.util.stream.Collectors; * <tr><td>{@link #KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT}</td> * <td>Integer</td><td><b>decoder-only</b>, optional, if content is MPEG-H audio, * specifies the preferred reference channel layout of the stream.</td></tr> + * <tr><td>{@link #KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE}</td><td>Integer</td><td>optional, used with + * large audio frame support, specifies max size of output buffer in bytes.</td></tr> + * <tr><td>{@link #KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE}</td><td>Integer</td><td>optional, + * used with large audio frame support, specifies threshold output size in bytes.</td></tr> * </table> * * Subtitle formats have the following keys: @@ -459,6 +464,50 @@ public final class MediaFormat { public static final String KEY_MAX_INPUT_SIZE = "max-input-size"; /** + * A key describing the maximum output buffer size in bytes when using + * large buffer mode containing multiple access units. + * + * When not-set - codec functions with one access-unit per frame. + * When set less than the size of two access-units - will make codec + * operate in single access-unit per output frame. + * When set to a value too big - The component or the framework will + * override this value to a reasonable max size not exceeding typical + * 10 seconds of data (device dependent) when set to a value larger than + * that. The value final value used will be returned in the output format. + * + * The associated value is an integer + * + * @see FEATURE_MultipleFrames + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size"; + + /** + * A key describing the threshold output size in bytes when using large buffer + * mode containing multiple access units. + * + * This is an optional parameter. + * + * If not set - the component can set this to a reasonable value. + * If set larger than max size, the components will + * clip this setting to maximum buffer batching output size. + * + * The component will return a partial output buffer if the output buffer reaches or + * surpass this limit. + * + * Threshold size should be always less or equal to KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE. + * The final setting of this value as determined by the component will be returned + * in the output format + * + * The associated value is an integer + * + * @see FEATURE_MultipleFrames + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE = + "buffer-batch-threshold-output-size"; + + /** * A key describing the pixel aspect ratio width. * The associated value is an integer */ diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 8e9c07996f83..a0f8ae5defeb 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1154,6 +1154,7 @@ public class MediaPlayer extends PlayerBase setDataSource(afd); return true; } catch (NullPointerException | SecurityException | IOException ex) { + Log.w(TAG, "Error setting data source via ContentResolver", ex); return false; } } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 17f25255fd4b..687feef6c58a 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -3109,9 +3109,8 @@ public final class MediaRouter2 { mStub, mDiscoveryPreference); } - if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { - unregisterRouterStubLocked(); - } + unregisterRouterStubIfNeededLocked(); + } catch (RemoteException ex) { Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); } @@ -3319,13 +3318,12 @@ public final class MediaRouter2 { obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller)); } - if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { - try { - unregisterRouterStubLocked(); - } catch (RemoteException ex) { - ex.rethrowFromSystemServer(); - } + try { + unregisterRouterStubIfNeededLocked(); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); } + } } @@ -3339,8 +3337,10 @@ public final class MediaRouter2 { } @GuardedBy("mLock") - private void unregisterRouterStubLocked() throws RemoteException { - if (mStub != null) { + private void unregisterRouterStubIfNeededLocked() throws RemoteException { + if (mStub != null + && mRouteCallbackRecords.isEmpty() + && mNonSystemRoutingControllers.isEmpty()) { mMediaRouterService.unregisterRouter2(mStub); mStub = null; } diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java index 74b6fc13ade4..71147f4becb5 100644 --- a/media/java/android/media/audiofx/Virtualizer.java +++ b/media/java/android/media/audiofx/Virtualizer.java @@ -46,6 +46,11 @@ import java.util.StringTokenizer; * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions. * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling * audio effects. + * + * @deprecated use the {@link android.media.Spatializer} class to query the capabilities of the + * platform with regards to spatialization, a different name for audio channel virtualization, + * and the {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)} to + * characterize how you want your content to be played when spatialization is supported. */ public class Virtualizer extends AudioEffect { diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 0f8a00a3c7d9..897827750d4e 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -44,6 +44,7 @@ oneway interface ITvInputClient { void onTrackSelected(int type, in String trackId, int seq); void onVideoAvailable(int seq); void onVideoUnavailable(int reason, int seq); + void onVideoFreezeUpdated(boolean isFrozen, int seq); void onContentAllowed(int seq); void onContentBlocked(in String rating, int seq); void onLayoutSurface(int left, int top, int right, int bottom, int seq); diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index a52e9a572dca..8e2702a50662 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -41,6 +41,7 @@ oneway interface ITvInputSessionCallback { void onTrackSelected(int type, in String trackId); void onVideoAvailable(); void onVideoUnavailable(int reason); + void onVideoFreezeUpdated(boolean isFrozen); void onContentAllowed(); void onContentBlocked(in String rating); void onLayoutSurface(int left, int top, int right, int bottom); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 51b2542688a6..caddd8ad674f 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -740,6 +740,15 @@ public final class TvInputManager { } /** + * This is called when the video freeze state has been updated. + * If {@code true}, the video is frozen on the last frame while audio playback continues. + * @param session A {@link TvInputManager.Session} associated with this callback. + * @param isFrozen Whether the video is frozen + */ + public void onVideoFreezeUpdated(Session session, boolean isFrozen) { + } + + /** * This is called when the current program content turns out to be allowed to watch since * its content rating is not blocked by parental controls. * @@ -1030,6 +1039,19 @@ public final class TvInputManager { }); } + void postVideoFreezeUpdated(boolean isFrozen) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onVideoFreezeUpdated(mSession, isFrozen); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyVideoFreezeUpdated(isFrozen); + } + } + }); + } + void postContentAllowed() { mHandler.post(new Runnable() { @Override @@ -1546,6 +1568,18 @@ public final class TvInputManager { } @Override + public void onVideoFreezeUpdated(boolean isFrozen, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postVideoFreezeUpdated(isFrozen); + } + } + + @Override public void onContentAllowed(int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 76d8e50f94be..6301a27bac4d 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -763,6 +763,34 @@ public abstract class TvInputService extends Service { } /** + * Informs the application that the video freeze state has been updated. + * + * When {@code true}, the video is frozen on the last frame but audio playback remains + * active. + * + * @param isFrozen Whether or not the video is frozen + * @hide + */ + public void notifyVideoFreezeUpdated(boolean isFrozen) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "notifyVideoFreezeUpdated"); + } + if (mSessionCallback != null) { + mSessionCallback.onVideoFreezeUpdated(isFrozen); + } + } catch (RemoteException e) { + Log.e(TAG, "error in notifyVideoFreezeUpdated", e); + } + } + }); + } + + /** * Sends an updated list of all audio presentations available from a Next Generation Audio * service. This is used by the framework to maintain the audio presentation information for * a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl index 4316d053a275..0f58b29247bb 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -88,6 +88,7 @@ interface ITvInteractiveAppManager { void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId); void notifyVideoAvailable(in IBinder sessionToken, int userId); void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId); + void notifyVideoFreezeUpdated(in IBinder sessionToken, boolean isFrozen, int userId); void notifyContentAllowed(in IBinder sessionToken, int userId); void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId); void notifySignalStrength(in IBinder sessionToken, int stength, int userId); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl index ba7cf13a7a1d..06808c9ff915 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl @@ -67,6 +67,7 @@ oneway interface ITvInteractiveAppSession { void notifyTracksChanged(in List<TvTrackInfo> tracks); void notifyVideoAvailable(); void notifyVideoUnavailable(int reason); + void notifyVideoFreezeUpdated(boolean isFrozen); void notifyContentAllowed(); void notifyContentBlocked(in String rating); void notifySignalStrength(int strength); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java index 518b08a93f95..77730aa46d0a 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java @@ -103,6 +103,7 @@ public class ITvInteractiveAppSessionWrapper private static final int DO_SEND_TIME_SHIFT_MODE = 46; private static final int DO_SEND_AVAILABLE_SPEEDS = 47; private static final int DO_SEND_SELECTED_TRACK_INFO = 48; + private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49; private final HandlerCaller mCaller; private Session mSessionImpl; @@ -364,6 +365,10 @@ public class ITvInteractiveAppSessionWrapper args.recycle(); break; } + case DO_NOTIFY_VIDEO_FREEZE_UPDATED: { + mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -552,6 +557,12 @@ public class ITvInteractiveAppSessionWrapper } @Override + public void notifyVideoFreezeUpdated(boolean isFrozen) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_VIDEO_FREEZE_UPDATED, + isFrozen)); + } + + @Override public void notifyContentAllowed() { mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_CONTENT_ALLOWED)); } diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index bf4379f470d8..8a340f6862bb 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -1731,6 +1731,22 @@ public final class TvInteractiveAppManager { } /** + * Notifies Interactive app session when the video freeze state is updated + * @param isFrozen Whether or not the video is frozen + */ + public void notifyVideoFreezeUpdated(boolean isFrozen) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyVideoFreezeUpdated(mToken, isFrozen, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Notifies Interactive APP session when content is allowed. */ public void notifyContentAllowed() { diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 79364034ac2a..5247a0ebe6e0 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -882,6 +882,15 @@ public abstract class TvInteractiveAppService extends Service { } /** + * Called when video becomes frozen or unfrozen. Audio playback will continue while + * video will be frozen to the last frame if {@code true}. + * @param isFrozen Whether or not the video is frozen. + * @hide + */ + public void onVideoFreezeUpdated(boolean isFrozen) { + } + + /** * Called when content is allowed. */ public void onContentAllowed() { @@ -1770,6 +1779,13 @@ public abstract class TvInteractiveAppService extends Service { onVideoUnavailable(reason); } + void notifyVideoFreezeUpdated(boolean isFrozen) { + if (DEBUG) { + Log.d(TAG, "notifyVideoFreezeUpdated (isFrozen=" + isFrozen + ")"); + } + onVideoFreezeUpdated(isFrozen); + } + void notifyContentAllowed() { if (DEBUG) { Log.d(TAG, "notifyContentAllowed"); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 40a12e4db4cc..5bb61c261ae2 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -719,6 +719,22 @@ public class TvInteractiveAppView extends ViewGroup { } /** + * Alerts the TV Interactive app that the video freeze state has been updated. + * If {@code true}, the video is frozen on the last frame while audio playback continues. + * + * @param isFrozen Whether the video is frozen. + * @hide + */ + public void notifyVideoFreezeUpdated(boolean isFrozen) { + if (DEBUG) { + Log.d(TAG, "notifyVideoFreezeUpdated"); + } + if (mSession != null) { + mSession.notifyVideoFreezeUpdated(isFrozen); + } + } + + /** * Sends signing result to related TV interactive app. * * <p>This is used when the corresponding server of the broadcast-independent interactive diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java index 74e5612c0486..4f6ede508f7a 100644 --- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java +++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java @@ -24,19 +24,16 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.content.res.AssetFileDescriptor; -import android.media.AudioAttributes; -import android.media.AudioFormat; -import android.media.AudioTrack; +import android.media.AudioManager; import android.media.IAudioService; -import android.media.LoudnessCodecConfigurator; -import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener; +import android.media.LoudnessCodecController; +import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; @@ -54,21 +51,19 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import java.util.List; import java.util.concurrent.Executors; /** - * Unit tests for {@link LoudnessCodecConfigurator} checking the internal interactions with a mocked + * Unit tests for {@link LoudnessCodecController} checking the internal interactions with a mocked * {@link IAudioService} without any real IPC interactions. */ @Presubmit @RunWith(AndroidJUnit4.class) -public class LoudnessCodecConfiguratorTest { +public class LoudnessCodecControllerTest { private static final String TAG = "LoudnessCodecConfiguratorTest"; private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/"; @@ -84,76 +79,41 @@ public class LoudnessCodecConfiguratorTest { @Mock private IAudioService mAudioService; - private LoudnessCodecConfigurator mLcc; + private LoudnessCodecController mLcc; + + private int mSessionId; @Before public void setUp() { - mLcc = LoudnessCodecConfigurator.createForTesting(mAudioService, - Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); - } - - @Test - @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setAudioTrack_callsAudioServiceStart() throws Exception { - final AudioTrack track = createAudioTrack(); - final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - - try { - mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), - anyList()); - } finally { - mediaCodec.release(); + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final AudioManager audioManager = (AudioManager) context.getSystemService( + AudioManager.class); + mSessionId = 0; + if (audioManager != null) { + mSessionId = audioManager.generateAudioSessionId(); } + mLcc = LoudnessCodecController.createForTesting(mSessionId, + Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() { + }, mAudioService); } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception { - when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle()); - final AudioTrack track = createAudioTrack(); - final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - - try { - mLcc.getLoudnessCodecParams(track, mediaCodec); - - verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any()); - } finally { - mediaCodec.release(); - } + public void createLcc_callsAudioServiceStart() throws Exception { + verify(mAudioService).startLoudnessCodecUpdates(eq(mSessionId)); } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setAudioTrack_addsAudioServicePiidCodecs() throws Exception { - final AudioTrack track = createAudioTrack(); - final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - - try { - mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); - } finally { - mediaCodec.release(); - } - } - - @Test - @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setAudioTrackTwice_ignoresSecondCall() throws Exception { - final AudioTrack track = createAudioTrack(); + public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception { + when(mAudioService.getLoudnessParams(any())).thenReturn(new PersistableBundle()); final MediaCodec mediaCodec = createAndConfigureMediaCodec(); try { mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - mLcc.setAudioTrack(track); + mLcc.getLoudnessCodecParams(mediaCodec); - verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()), - anyList()); + verify(mAudioService).getLoudnessParams(any()); } finally { mediaCodec.release(); } @@ -161,16 +121,14 @@ public class LoudnessCodecConfiguratorTest { @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setTrackNull_stopCodecUpdates() throws Exception { - final AudioTrack track = createAudioTrack(); + public void release_stopCodecUpdates() throws Exception { final MediaCodec mediaCodec = createAndConfigureMediaCodec(); try { mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); + mLcc.release(); // stops updats - mLcc.setAudioTrack(null); // stops updates - verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId)); } finally { mediaCodec.release(); } @@ -204,40 +162,14 @@ public class LoudnessCodecConfiguratorTest { @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception { - final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class); - final AudioTrack track = createAudioTrack(); - final MediaCodec mediaCodec1 = createAndConfigureMediaCodec(); - final MediaCodec mediaCodec2 = createAndConfigureMediaCodec(); - - try { - mLcc.addMediaCodec(mediaCodec1); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), - argument.capture()); - assertEquals(argument.getValue().size(), 1); - - mLcc.addMediaCodec(mediaCodec2); - mLcc.setAudioTrack(null); - verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); - } finally { - mediaCodec1.release(); - mediaCodec2.release(); - } - } - - @Test - @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception { - final AudioTrack track = createAudioTrack(); final MediaCodec mediaCodec = createAndConfigureMediaCodec(); try { mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); mLcc.removeMediaCodec(mediaCodec); - verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any()); } finally { mediaCodec.release(); } @@ -245,37 +177,28 @@ public class LoudnessCodecConfiguratorTest { @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception { - final AudioTrack track = createAudioTrack(); - final MediaCodec mediaCodec1 = createAndConfigureMediaCodec(); - final MediaCodec mediaCodec2 = createAndConfigureMediaCodec(); + public void addMediaCodec_callsAudioServiceAdd() throws Exception { + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); try { - mLcc.addMediaCodec(mediaCodec1); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); - - mLcc.addMediaCodec(mediaCodec2); - verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any()); + mLcc.addMediaCodec(mediaCodec); + verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any()); } finally { - mediaCodec1.release(); - mediaCodec2.release(); + mediaCodec.release(); } } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void removeMediaCodecAfterSetTrack_callsAudioServiceRemove() throws Exception { - final AudioTrack track = createAudioTrack(); + public void removeMediaCodec_callsAudioServiceRemove() throws Exception { final MediaCodec mediaCodec = createAndConfigureMediaCodec(); try { mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any()); mLcc.removeMediaCodec(mediaCodec); - verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any()); } finally { mediaCodec.release(); } @@ -283,15 +206,13 @@ public class LoudnessCodecConfiguratorTest { @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) - public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception { - final AudioTrack track = createAudioTrack(); + public void removeWrongMediaCodec_triggersIAE() throws Exception { final MediaCodec mediaCodec1 = createAndConfigureMediaCodec(); final MediaCodec mediaCodec2 = createAndConfigureMediaCodec(); try { mLcc.addMediaCodec(mediaCodec1); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any()); assertThrows(IllegalArgumentException.class, () -> mLcc.removeMediaCodec(mediaCodec2)); @@ -301,16 +222,6 @@ public class LoudnessCodecConfiguratorTest { } } - private static AudioTrack createAudioTrack() { - return new AudioTrack.Builder() - .setAudioAttributes(new AudioAttributes.Builder().build()) - .setBufferSizeInBytes(TEST_AUDIO_TRACK_BUFFER_SIZE) - .setAudioFormat(new AudioFormat.Builder() - .setChannelMask(TEST_AUDIO_TRACK_CHANNELS) - .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE).build()) - .build(); - } - private MediaCodec createAndConfigureMediaCodec() throws Exception { AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext() .getResources() @@ -320,7 +231,7 @@ public class LoudnessCodecConfiguratorTest { extractor = new MediaExtractor(); try { extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(), - testFd.getLength()); + testFd.getLength()); assertEquals("wrong number of tracks", 1, extractor.getTrackCount()); MediaFormat format = extractor.getTrackFormat(0); String mime = format.getString(MediaFormat.KEY_MIME); diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 7573474f65b4..1046d8e9aebb 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -72,7 +72,7 @@ package android.nfc { method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); - method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo(); + method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported(); @@ -175,18 +175,18 @@ package android.nfc { ctor public TagLostException(String); } - @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable { - ctor public WlcLDeviceInfo(double, double, double, int); + @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcListenerDeviceInfo implements android.os.Parcelable { + ctor public WlcListenerDeviceInfo(int, double, double, int); method public int describeContents(); - method public double getBatteryLevel(); - method public double getProductId(); + method @FloatRange(from=0.0, to=100.0) public double getBatteryLevel(); + method public int getProductId(); method public int getState(); method public double getTemperature(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int CONNECTED_CHARGING = 2; // 0x2 - field public static final int CONNECTED_DISCHARGING = 3; // 0x3 - field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR; - field public static final int DISCONNECTED = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcListenerDeviceInfo> CREATOR; + field public static final int STATE_CONNECTED_CHARGING = 2; // 0x2 + field public static final int STATE_CONNECTED_DISCHARGING = 3; // 0x3 + field public static final int STATE_DISCONNECTED = 1; // 0x1 } } diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 40672a1adc15..dc2a625aacfd 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -8,7 +8,6 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); - method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState(); method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn(); @@ -20,6 +19,7 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean); + method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener); field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC"; @@ -37,7 +37,7 @@ package android.nfc { } @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener { - method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo); + method public void onWlcStateChanged(@NonNull android.nfc.WlcListenerDeviceInfo); } } diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index bec62c5b1b82..63c3414acc36 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -32,8 +32,8 @@ import android.nfc.ITagRemovedCallback; import android.nfc.INfcDta; import android.nfc.INfcWlcStateListener; import android.nfc.NfcAntennaInfo; +import android.nfc.WlcListenerDeviceInfo; import android.os.Bundle; -import android.nfc.WlcLDeviceInfo; /** * @hide @@ -90,11 +90,11 @@ interface INfcAdapter boolean setObserveMode(boolean enabled); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") - boolean enableWlc(boolean enable); + boolean setWlcEnabled(boolean enable); boolean isWlcEnabled(); void registerWlcStateListener(in INfcWlcStateListener listener); void unregisterWlcStateListener(in INfcWlcStateListener listener); - WlcLDeviceInfo getWlcLDeviceInfo(); + WlcListenerDeviceInfo getWlcListenerDeviceInfo(); void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags); diff --git a/nfc/java/android/nfc/INfcWlcStateListener.aidl b/nfc/java/android/nfc/INfcWlcStateListener.aidl index c2b7075bc6e4..584eb9a128b4 100644 --- a/nfc/java/android/nfc/INfcWlcStateListener.aidl +++ b/nfc/java/android/nfc/INfcWlcStateListener.aidl @@ -16,7 +16,7 @@ package android.nfc; -import android.nfc.WlcLDeviceInfo; +import android.nfc.WlcListenerDeviceInfo; /** * @hide */ @@ -24,7 +24,7 @@ oneway interface INfcWlcStateListener { /** * Called whenever NFC WLC state changes * - * @param wlcLDeviceInfo NFC wlc listener information + * @param wlcListenerDeviceInfo NFC wlc listener information */ - void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo); + void onWlcStateChanged(in WlcListenerDeviceInfo wlcListenerDeviceInfo); } diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index 68c16e69f3af..11eb97b440bf 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -2820,13 +2820,12 @@ public final class NfcAdapter { @SystemApi @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - public boolean enableWlc(boolean enable) { + public boolean setWlcEnabled(boolean enable) { if (!sHasNfcWlcFeature) { throw new UnsupportedOperationException(); } try { - return sService.enableWlc(enable); - + return sService.setWlcEnabled(enable); } catch (RemoteException e) { attemptDeadServiceRecovery(e); // Try one more time @@ -2835,7 +2834,7 @@ public final class NfcAdapter { return false; } try { - return sService.enableWlc(enable); + return sService.setWlcEnabled(enable); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover NFC Service."); } @@ -2887,7 +2886,7 @@ public final class NfcAdapter { /** * Called on NFC WLC state changes */ - void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo); + void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo); } /** @@ -2945,12 +2944,12 @@ public final class NfcAdapter { */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING) @Nullable - public WlcLDeviceInfo getWlcLDeviceInfo() { + public WlcListenerDeviceInfo getWlcListenerDeviceInfo() { if (!sHasNfcWlcFeature) { throw new UnsupportedOperationException(); } try { - return sService.getWlcLDeviceInfo(); + return sService.getWlcListenerDeviceInfo(); } catch (RemoteException e) { attemptDeadServiceRecovery(e); // Try one more time @@ -2959,7 +2958,7 @@ public final class NfcAdapter { return null; } try { - return sService.getWlcLDeviceInfo(); + return sService.getWlcListenerDeviceInfo(); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover NFC Service."); } diff --git a/nfc/java/android/nfc/NfcWlcStateListener.java b/nfc/java/android/nfc/NfcWlcStateListener.java index 8d793101f41f..890cb090f587 100644 --- a/nfc/java/android/nfc/NfcWlcStateListener.java +++ b/nfc/java/android/nfc/NfcWlcStateListener.java @@ -36,7 +36,7 @@ public class NfcWlcStateListener extends INfcWlcStateListener.Stub { private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>(); - private WlcLDeviceInfo mCurrentState = null; + private WlcListenerDeviceInfo mCurrentState = null; private boolean mIsRegistered = false; public NfcWlcStateListener(@NonNull INfcAdapter adapter) { @@ -98,8 +98,10 @@ public class NfcWlcStateListener extends INfcWlcStateListener.Stub { Executor executor = mListenerMap.get(listener); final long identity = Binder.clearCallingIdentity(); try { - executor.execute(() -> listener.onWlcStateChanged( - mCurrentState)); + if (Flags.enableNfcCharging()) { + executor.execute(() -> listener.onWlcStateChanged( + mCurrentState)); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -107,9 +109,9 @@ public class NfcWlcStateListener extends INfcWlcStateListener.Stub { } @Override - public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) { + public void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo) { synchronized (this) { - mCurrentState = wlcLDeviceInfo; + mCurrentState = wlcListenerDeviceInfo; for (WlcStateListener cb : mListenerMap.keySet()) { sendCurrentState(cb); diff --git a/nfc/java/android/nfc/WlcLDeviceInfo.java b/nfc/java/android/nfc/WlcLDeviceInfo.java deleted file mode 100644 index 016431e90d8e..000000000000 --- a/nfc/java/android/nfc/WlcLDeviceInfo.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.nfc; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Contains information of the nfc wireless charging listener device information. - */ -@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING) -public final class WlcLDeviceInfo implements Parcelable { - public static final int DISCONNECTED = 1; - - public static final int CONNECTED_CHARGING = 2; - - public static final int CONNECTED_DISCHARGING = 3; - - private double mProductId; - private double mTemperature; - private double mBatteryLevel; - private int mState; - - public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) { - this.mProductId = productId; - this.mTemperature = temperature; - this.mBatteryLevel = batteryLevel; - this.mState = state; - } - - /** - * ProductId of the WLC listener device. - */ - public double getProductId() { - return mProductId; - } - - /** - * Temperature of the WLC listener device. - */ - public double getTemperature() { - return mTemperature; - } - - /** - * BatteryLevel of the WLC listener device. - */ - public double getBatteryLevel() { - return mBatteryLevel; - } - - /** - * State of the WLC listener device. - */ - public int getState() { - return mState; - } - - private WlcLDeviceInfo(Parcel in) { - this.mProductId = in.readDouble(); - this.mTemperature = in.readDouble(); - this.mBatteryLevel = in.readDouble(); - this.mState = in.readInt(); - } - - public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR = - new Parcelable.Creator<WlcLDeviceInfo>() { - @Override - public WlcLDeviceInfo createFromParcel(Parcel in) { - return new WlcLDeviceInfo(in); - } - - @Override - public WlcLDeviceInfo[] newArray(int size) { - return new WlcLDeviceInfo[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeDouble(mProductId); - dest.writeDouble(mTemperature); - dest.writeDouble(mBatteryLevel); - dest.writeDouble(mState); - } -} diff --git a/nfc/java/android/nfc/WlcLDeviceInfo.aidl b/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl index 33143fe81162..7f2ca545007b 100644 --- a/nfc/java/android/nfc/WlcLDeviceInfo.aidl +++ b/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl @@ -16,4 +16,4 @@ package android.nfc; -parcelable WlcLDeviceInfo; +parcelable WlcListenerDeviceInfo; diff --git a/nfc/java/android/nfc/WlcListenerDeviceInfo.java b/nfc/java/android/nfc/WlcListenerDeviceInfo.java new file mode 100644 index 000000000000..45315f812250 --- /dev/null +++ b/nfc/java/android/nfc/WlcListenerDeviceInfo.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Contains information of the nfc wireless charging listener device information. + */ +@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING) +public final class WlcListenerDeviceInfo implements Parcelable { + /** + * Device is currently not connected with any WlcListenerDevice. + */ + public static final int STATE_DISCONNECTED = 1; + + /** + * Device is currently connected with a WlcListenerDevice and is charging it. + */ + public static final int STATE_CONNECTED_CHARGING = 2; + + /** + * Device is currently connected with a WlcListenerDevice without charging it. + */ + public static final int STATE_CONNECTED_DISCHARGING = 3; + + /** + * Possible states from {@link #getState}. + * @hide + */ + @IntDef(prefix = { "STATE_" }, value = { + STATE_DISCONNECTED, + STATE_CONNECTED_CHARGING, + STATE_CONNECTED_DISCHARGING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface WlcListenerState{} + + private int mProductId; + private double mTemperature; + private double mBatteryLevel; + private int mState; + + /** + * Create a new object containing wlc listener information. + * + * @param productId code for the device vendor + * @param temperature current temperature + * @param batteryLevel current battery level + * @param state current state + */ + public WlcListenerDeviceInfo(int productId, double temperature, double batteryLevel, + @WlcListenerState int state) { + this.mProductId = productId; + this.mTemperature = temperature; + this.mBatteryLevel = batteryLevel; + this.mState = state; + } + + /** + * ProductId of the WLC listener device. + * @return integer that is converted from USI Stylus VendorID[11:0]. + */ + public int getProductId() { + return mProductId; + } + + /** + * Temperature of the WLC listener device. + * @return the value represents the temperature in °C. + */ + public double getTemperature() { + return mTemperature; + } + + /** + * BatteryLevel of the WLC listener device. + * @return battery level in percentage [0-100] + */ + public @FloatRange(from = 0.0, to = 100.0) double getBatteryLevel() { + return mBatteryLevel; + } + + /** + * State of the WLC listener device. + */ + public @WlcListenerState int getState() { + return mState; + } + + private WlcListenerDeviceInfo(Parcel in) { + this.mProductId = in.readInt(); + this.mTemperature = in.readDouble(); + this.mBatteryLevel = in.readDouble(); + this.mState = in.readInt(); + } + + public static final @NonNull Parcelable.Creator<WlcListenerDeviceInfo> CREATOR = + new Parcelable.Creator<WlcListenerDeviceInfo>() { + @Override + public WlcListenerDeviceInfo createFromParcel(Parcel in) { + return new WlcListenerDeviceInfo(in); + } + + @Override + public WlcListenerDeviceInfo[] newArray(int size) { + return new WlcListenerDeviceInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mProductId); + dest.writeDouble(mTemperature); + dest.writeDouble(mBatteryLevel); + dest.writeInt(mState); + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt index ba8e354fa0c6..b34c310cd30f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt @@ -54,16 +54,18 @@ fun SettingsExposedDropdownMenuCheckBox( selectedOptionsState: SnapshotStateList<Int>, emptyVal: String = "", enabled: Boolean, + errorMessage: String? = null, onSelectedOptionStateChange: () -> Unit, ) { var dropDownWidth by remember { mutableIntStateOf(0) } var expanded by remember { mutableStateOf(false) } + val allIndex = options.indexOf("*") ExposedDropdownMenuBox( expanded = expanded, onExpandedChange = { expanded = it }, modifier = Modifier .width(350.dp) - .padding(SettingsDimension.menuFieldPadding) + .padding(SettingsDimension.textFieldPadding) .onSizeChanged { dropDownWidth = it.width }, ) { OutlinedTextField( @@ -72,7 +74,8 @@ fun SettingsExposedDropdownMenuCheckBox( .menuAnchor() .fillMaxWidth(), value = if (selectedOptionsState.size == 0) emptyVal - else selectedOptionsState.joinToString { options[it] }, + else if (selectedOptionsState.contains(allIndex)) "*" + else selectedOptionsState.joinToString { options[it] }, onValueChange = {}, label = { Text(text = label) }, trailingIcon = { @@ -81,7 +84,13 @@ fun SettingsExposedDropdownMenuCheckBox( ) }, readOnly = true, - enabled = enabled + enabled = enabled, + isError = errorMessage != null, + supportingText = { + if (errorMessage != null) { + Text(text = errorMessage) + } + } ) if (options.isNotEmpty()) { ExposedDropdownMenu( @@ -98,9 +107,17 @@ fun SettingsExposedDropdownMenuCheckBox( .fillMaxWidth(), onClick = { if (selectedOptionsState.contains(index)) { - selectedOptionsState.remove( - index - ) + if (index == allIndex) + selectedOptionsState.clear() + else { + selectedOptionsState.remove( + index + ) + if (selectedOptionsState.contains(allIndex)) + selectedOptionsState.remove( + allIndex + ) + } } else { selectedOptionsState.add( index diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index 57012aabb123..931a6f149b84 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -3,17 +3,17 @@ */ /* 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. -*/ + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.settingslib.bluetooth; @@ -23,6 +23,7 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; @@ -30,6 +31,7 @@ import android.content.Context; import android.os.Build; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -57,13 +59,12 @@ public class LeAudioProfile implements LocalBluetoothProfile { private static final int ORDINAL = 1; // These callbacks run on the main thread. - private final class LeAudioServiceListener - implements BluetoothProfile.ServiceListener { + private final class LeAudioServiceListener implements BluetoothProfile.ServiceListener { @RequiresApi(Build.VERSION_CODES.S) public void onServiceConnected(int profile, BluetoothProfile proxy) { if (DEBUG) { - Log.d(TAG,"Bluetooth service connected"); + Log.d(TAG, "Bluetooth service connected"); } mService = (BluetoothLeAudio) proxy; // We just bound to the service, so refresh the UI for any connected LeAudio devices. @@ -78,8 +79,7 @@ public class LeAudioProfile implements LocalBluetoothProfile { } device = mDeviceManager.addDevice(nextDevice); } - device.onProfileStateChanged(LeAudioProfile.this, - BluetoothProfile.STATE_CONNECTED); + device.onProfileStateChanged(LeAudioProfile.this, BluetoothProfile.STATE_CONNECTED); device.refresh(); } @@ -89,7 +89,7 @@ public class LeAudioProfile implements LocalBluetoothProfile { public void onServiceDisconnected(int profile) { if (DEBUG) { - Log.d(TAG,"Bluetooth service disconnected"); + Log.d(TAG, "Bluetooth service disconnected"); } mProfileManager.callServiceDisconnectedListeners(); mIsProfileReady = false; @@ -105,7 +105,9 @@ public class LeAudioProfile implements LocalBluetoothProfile { return BluetoothProfile.LE_AUDIO; } - LeAudioProfile(Context context, CachedBluetoothDeviceManager deviceManager, + LeAudioProfile( + Context context, + CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager) { mContext = context; mDeviceManager = deviceManager; @@ -113,8 +115,7 @@ public class LeAudioProfile implements LocalBluetoothProfile { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter.getProfileProxy( - context, new LeAudioServiceListener(), - BluetoothProfile.LE_AUDIO); + context, new LeAudioServiceListener(), BluetoothProfile.LE_AUDIO); } public boolean accessProfileEnabled() { @@ -126,18 +127,22 @@ public class LeAudioProfile implements LocalBluetoothProfile { } public List<BluetoothDevice> getConnectedDevices() { - return getDevicesByStates(new int[] { - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}); + return getDevicesByStates( + new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING + }); } public List<BluetoothDevice> getConnectableDevices() { - return getDevicesByStates(new int[] { - BluetoothProfile.STATE_DISCONNECTED, - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}); + return getDevicesByStates( + new int[] { + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING + }); } private List<BluetoothDevice> getDevicesByStates(int[] states) { @@ -148,8 +153,8 @@ public class LeAudioProfile implements LocalBluetoothProfile { } /* - * @hide - */ + * @hide + */ public boolean connect(BluetoothDevice device) { if (mService == null) { return false; @@ -158,8 +163,8 @@ public class LeAudioProfile implements LocalBluetoothProfile { } /* - * @hide - */ + * @hide + */ public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; @@ -174,6 +179,14 @@ public class LeAudioProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } + /** Get group id for {@link BluetoothDevice}. */ + public int getGroupId(@NonNull BluetoothDevice device) { + if (mService == null) { + return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; + } + return mService.getGroupId(device); + } + public boolean setActiveDevice(BluetoothDevice device) { if (mBluetoothAdapter == null) { return false; @@ -193,29 +206,28 @@ public class LeAudioProfile implements LocalBluetoothProfile { /** * Get Lead device for the group. * - * Lead device is the device that can be used as an active device in the system. - * Active devices points to the Audio Device for the Le Audio group. - * This method returns the Lead devices for the connected LE Audio - * group and this device should be used in the setActiveDevice() method by other parts - * of the system, which wants to set to active a particular Le Audio group. + * <p>Lead device is the device that can be used as an active device in the system. Active + * devices points to the Audio Device for the Le Audio group. This method returns the Lead + * devices for the connected LE Audio group and this device should be used in the + * setActiveDevice() method by other parts of the system, which wants to set to active a + * particular Le Audio group. * - * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group. + * <p>Note: getActiveDevice() returns the Lead device for the currently active LE Audio group. * Note: When Lead device gets disconnected while Le Audio group is active and has more devices - * in the group, then Lead device will not change. If Lead device gets disconnected, for the - * Le Audio group which is not active, a new Lead device will be chosen + * in the group, then Lead device will not change. If Lead device gets disconnected, for the Le + * Audio group which is not active, a new Lead device will be chosen * * @param groupId The group id. * @return group lead device. - * * @hide */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) { if (DEBUG) { - Log.d(TAG,"getConnectedGroupLeadDevice"); + Log.d(TAG, "getConnectedGroupLeadDevice"); } if (mService == null) { - Log.e(TAG,"No service."); + Log.e(TAG, "No service."); return null; } return mService.getConnectedGroupLeadDevice(groupId); @@ -310,10 +322,10 @@ public class LeAudioProfile implements LocalBluetoothProfile { } if (mService != null) { try { - BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.LE_AUDIO, - mService); + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.LE_AUDIO, mService); mService = null; - }catch (Throwable t) { + } catch (Throwable t) { Log.w(TAG, "Error cleaning up LeAudio proxy", t); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index de21c541c7e4..934870507a20 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -84,6 +84,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO), Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE), Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME), + Settings.Secure.getUriFor( + Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY), }; private BluetoothLeBroadcast mServiceBroadcast; @@ -96,6 +98,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { private String mNewAppSourceName = ""; private boolean mIsBroadcastProfileReady = false; private boolean mIsBroadcastAssistantProfileReady = false; + private boolean mImproveCompatibility = false; private String mProgramInfo; private byte[] mBroadcastCode; private Executor mExecutor; @@ -391,6 +394,52 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { * <p>If the system started the LE Broadcast, then the system calls the corresponding callback * {@link BluetoothLeBroadcast.Callback}. */ + public void startPrivateBroadcast() { + mNewAppSourceName = "Sharing audio"; + if (mServiceBroadcast == null) { + Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast."); + return; + } + if (mServiceBroadcast.getAllBroadcastMetadata().size() + >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) { + Log.d(TAG, "Skip starting the broadcast due to number limit."); + return; + } + String programInfo = getProgramInfo(); + boolean improveCompatibility = getImproveCompatibility(); + if (DEBUG) { + Log.d( + TAG, + "startBroadcast: language = null , programInfo = " + + programInfo + + ", improveCompatibility = " + + improveCompatibility); + } + // Current broadcast framework only support one subgroup + BluetoothLeBroadcastSubgroupSettings subgroupSettings = + buildBroadcastSubgroupSettings( + /* language= */ null, programInfo, improveCompatibility); + BluetoothLeBroadcastSettings settings = + buildBroadcastSettings( + true, // TODO: set to false after framework fix + TextUtils.isEmpty(programInfo) ? null : programInfo, + (mBroadcastCode != null && mBroadcastCode.length > 0) + ? mBroadcastCode + : null, + ImmutableList.of(subgroupSettings)); + mServiceBroadcast.startBroadcast(settings); + } + + /** + * Start the private Broadcast for personal audio sharing or qr code sharing. + * + * <p>The broadcast will use random string for both broadcast name and subgroup program info; + * The broadcast will use random string for broadcast code; The broadcast will only have one + * subgroup due to system limitation; The subgroup language will be null. + * + * <p>If the system started the LE Broadcast, then the system calls the corresponding callback + * {@link BluetoothLeBroadcast.Callback}. + */ public void startPrivateBroadcast(int quality) { mNewAppSourceName = "Sharing audio"; if (mServiceBroadcast == null) { @@ -408,7 +457,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } // Current broadcast framework only support one subgroup BluetoothLeBroadcastSubgroupSettings subgroupSettings = - buildBroadcastSubgroupSettings(/* language= */ null, programInfo, quality); + buildBroadcastSubgroupSettings( + /* language= */ null, + programInfo, + /* improveCompatibility= */ + BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD == quality); BluetoothLeBroadcastSettings settings = buildBroadcastSettings( true, // TODO: set to false after framework fix @@ -437,7 +490,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } private BluetoothLeBroadcastSubgroupSettings buildBroadcastSubgroupSettings( - @Nullable String language, @Nullable String programInfo, int quality) { + @Nullable String language, @Nullable String programInfo, boolean improveCompatibility) { BluetoothLeAudioContentMetadata metadata = new BluetoothLeAudioContentMetadata.Builder() .setLanguage(language) @@ -447,7 +500,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { // metadata to keep legacy UI working. mBluetoothLeAudioContentMetadata = metadata; return new BluetoothLeBroadcastSubgroupSettings.Builder() - .setPreferredQuality(quality) + .setPreferredQuality( + improveCompatibility + ? BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD + : BluetoothLeBroadcastSubgroupSettings.QUALITY_HIGH) .setContentMetadata(mBluetoothLeAudioContentMetadata) .build(); } @@ -513,6 +569,36 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } } + /** Get compatibility config for broadcast. */ + public boolean getImproveCompatibility() { + return mImproveCompatibility; + } + + /** Set compatibility config for broadcast. */ + public void setImproveCompatibility(boolean improveCompatibility) { + setImproveCompatibility(improveCompatibility, /* updateContentResolver= */ true); + } + + private void setImproveCompatibility( + boolean improveCompatibility, boolean updateContentResolver) { + if (mImproveCompatibility == improveCompatibility) { + Log.d(TAG, "setImproveCompatibility: improveCompatibility is not changed"); + return; + } + mImproveCompatibility = improveCompatibility; + if (updateContentResolver) { + if (mContentResolver == null) { + Log.d(TAG, "mContentResolver is null"); + return; + } + Log.d(TAG, "Set improveCompatibility to: " + improveCompatibility); + Settings.Secure.putString( + mContentResolver, + Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY, + improveCompatibility ? "1" : "0"); + } + } + private void setLatestBroadcastId(int broadcastId) { Log.d(TAG, "setLatestBroadcastId: mBroadcastId is " + broadcastId); mBroadcastId = broadcastId; @@ -600,6 +686,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Settings.Secure.getString( mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME); setAppSourceName(appSourceName, /* updateContentResolver= */ false); + + String improveCompatibility = + Settings.Secure.getString( + mContentResolver, + Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY); + setImproveCompatibility( + improveCompatibility == null ? false : improveCompatibility.equals("1"), + /* updateContentResolver= */ false); } private void updateBroadcastInfoFromBroadcastMetadata( diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index d6f1eab442fa..15f33d2cff42 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -229,6 +229,17 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getId() { + if (com.android.media.flags.Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + // Note: be careful when removing this flag. Instead of just removing it, you might want + // to replace it with SDK_INT >= 35. Explanation: The presence of SDK checks in settings + // lib suggests that a mainline component may depend on this code. Which means removing + // this "if" (and using always the route info id) could mean a regression on mainline + // code running on a device that's running API 34 or older. Unfortunately, we cannot + // check the API level at the moment of writing this code because the API level has not + // been bumped, yet. + return mRouteInfo.getId(); + } + String id; switch (mRouteInfo.getType()) { case TYPE_WIRED_HEADSET: diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 1746befbfa4d..ceba9be70487 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -31,10 +31,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.media.MediaRoute2Info; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import com.android.media.flags.Flags; import com.android.settingslib.R; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,6 +50,8 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class PhoneMediaDeviceTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private MediaRoute2Info mInfo; @@ -110,8 +117,18 @@ public class PhoneMediaDeviceTest { .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name)); } + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + @Test + public void getId_whenAdvancedWiredRoutingEnabled_returnCorrectId() { + String fakeId = "foo"; + when(mInfo.getId()).thenReturn(fakeId); + + assertThat(mPhoneMediaDevice.getId()).isEqualTo(fakeId); + } + + @DisableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) @Test - public void getId_returnCorrectId() { + public void getId_whenAdvancedWiredRoutingDisabled_returnCorrectId() { when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES); assertThat(mPhoneMediaDevice.getId()) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 2d442f4c0e6e..3a46f4e96ccb 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -406,7 +406,7 @@ public class SettingsProvider extends ContentProvider { Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); - mSettingsRegistry = new SettingsRegistry(); + mSettingsRegistry = new SettingsRegistry(mHandlerThread.getLooper()); } SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext()); synchronized (mLock) { @@ -2896,8 +2896,8 @@ public class SettingsProvider extends ContentProvider { private String mSettingsCreationBuildId; - public SettingsRegistry() { - mHandler = new MyHandler(getContext().getMainLooper()); + SettingsRegistry(Looper looper) { + mHandler = new MyHandler(looper); mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers()); mBackupManager = new BackupManager(getContext()); } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 323613077a70..2c35c777ab12 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -89,6 +89,13 @@ flag { } flag { + name: "notification_avalanche_suppression" + namespace: "systemui" + description: "After notification avalanche floodgate event, suppress HUNs completely." + bug: "321089634" +} + +flag { name: "notification_background_tint_optimization" namespace: "systemui" description: "Re-enable the codepath that removed tinting of notifications when the" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt index ccf119ab3088..97c407cb9b16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt @@ -41,10 +41,10 @@ import org.junit.runner.RunWith class FingerprintPropertyInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest = kosmos.fingerprintPropertyInteractor - private val repository = kosmos.fingerprintPropertyRepository - private val configurationRepository = kosmos.fakeConfigurationRepository - private val displayRepository = kosmos.displayRepository + private val underTest by lazy { kosmos.fingerprintPropertyInteractor } + private val repository by lazy { kosmos.fingerprintPropertyRepository } + private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } + private val displayRepository by lazy { kosmos.displayRepository } @Test fun sensorLocation_resolution1f() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt index c8560c31cdf1..c300e0a905b6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt @@ -55,7 +55,9 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor - private val mainHandler = FakeHandler(Looper.getMainLooper()) + private val mainHandler by lazy { + FakeHandler(Looper.getMainLooper()) + } private lateinit var underTest: PrimaryBouncerInteractor @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 27b84b2ffabc..d30e33332926 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -39,8 +39,8 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val bouncerInteractor = kosmos.bouncerInteractor - private val underTest = + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } + private val underTest by lazy { PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, @@ -49,6 +49,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Pin, ) + } @Test fun animateFailure() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index cfe8c5d52c18..73db1757c06a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -58,8 +58,8 @@ class BouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val authenticationInteractor = kosmos.authenticationInteractor - private val bouncerInteractor = kosmos.bouncerInteractor + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } private lateinit var underTest: BouncerViewModel @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index a0c2acc31589..cddbd1f78dd5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -66,7 +66,9 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor lateinit var bouncerInteractor: PrimaryBouncerInteractor - private val mainHandler = FakeHandler(Looper.getMainLooper()) + private val mainHandler by lazy { + FakeHandler(Looper.getMainLooper()) + } val repository = FakeKeyguardBouncerRepository() lateinit var underTest: KeyguardBouncerViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index b3b6457b46e7..c6d612d5dc79 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -52,17 +52,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val authenticationInteractor = kosmos.authenticationInteractor - private val sceneInteractor = kosmos.sceneInteractor - private val bouncerInteractor = kosmos.bouncerInteractor - private val bouncerViewModel = kosmos.bouncerViewModel + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } + private val bouncerViewModel by lazy { kosmos.bouncerViewModel } private val isInputEnabled = MutableStateFlow(true) - private val underTest = + private val underTest by lazy { PasswordBouncerViewModel( viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled.asStateFlow(), ) + } @Before fun setUp() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index c2680bcc82a3..725bdbd43445 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -53,17 +53,18 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val authenticationInteractor = kosmos.authenticationInteractor - private val sceneInteractor = kosmos.sceneInteractor - private val bouncerInteractor = kosmos.bouncerInteractor - private val bouncerViewModel = kosmos.bouncerViewModel - private val underTest = + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } + private val bouncerViewModel by lazy { kosmos.bouncerViewModel } + private val underTest by lazy { PatternBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), ) + } private val containerSize = 90 // px private val dotSize = 30 // px diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 1d660d63710d..06e12586d384 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -53,22 +53,24 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val authenticationInteractor = kosmos.authenticationInteractor - private val bouncerInteractor = kosmos.bouncerInteractor - private val bouncerViewModel = kosmos.bouncerViewModel - private val underTest = - PinBouncerViewModel( - applicationContext = context, - viewModelScope = testScope.backgroundScope, - interactor = bouncerInteractor, - isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = kosmos.simBouncerInteractor, - authenticationMethod = AuthenticationMethodModel.Pin, - ) + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } + private val bouncerViewModel by lazy { kosmos.bouncerViewModel } + private lateinit var underTest: PinBouncerViewModel @Before fun setUp() { + underTest = + PinBouncerViewModel( + applicationContext = context, + viewModelScope = testScope.backgroundScope, + interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), + simBouncerInteractor = kosmos.simBouncerInteractor, + authenticationMethod = AuthenticationMethodModel.Pin, + ) + overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN) overrideResource(R.string.kg_wrong_pin, WRONG_PIN) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt index ed29aa4ac202..e8216735fb5d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.communalInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 21190608e002..86279ef24ca7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.domain.interactor import android.app.smartspace.SmartspaceTarget +import android.content.pm.UserInfo import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -42,12 +43,12 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.communalInteractor -import com.android.systemui.keyguard.domain.interactor.editWidgetsActivityStarter import com.android.systemui.kosmos.testScope import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -59,8 +60,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations /** * This class of test cases assume that communal is enabled. For disabled cases, see @@ -70,6 +73,9 @@ import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class CommunalInteractorTest : SysuiTestCase() { + @Mock private lateinit var mainUser: UserInfo + @Mock private lateinit var secondaryUser: UserInfo + private val kosmos = testKosmos() private val testScope = kosmos.testScope @@ -78,6 +84,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var userRepository: FakeUserRepository private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter @@ -86,15 +93,22 @@ class CommunalInteractorTest : SysuiTestCase() { @Before fun setUp() { + MockitoAnnotations.initMocks(this) + tutorialRepository = kosmos.fakeCommunalTutorialRepository communalRepository = kosmos.fakeCommunalRepository mediaRepository = kosmos.fakeCommunalMediaRepository widgetRepository = kosmos.fakeCommunalWidgetRepository smartspaceRepository = kosmos.fakeSmartspaceRepository + userRepository = kosmos.fakeUserRepository keyguardRepository = kosmos.fakeKeyguardRepository editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter communalPrefsRepository = kosmos.fakeCommunalPrefsRepository + whenever(mainUser.isMain).thenReturn(true) + whenever(secondaryUser.isMain).thenReturn(false) + userRepository.setUserInfos(listOf(mainUser, secondaryUser)) + underTest = kosmos.communalInteractor } @@ -103,36 +117,52 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() } @Test - fun isCommunalAvailable_trueWhenStorageUnlock() = + fun isCommunalAvailable_storageUnlockedAndMainUser_true() = testScope.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() keyguardRepository.setIsEncryptedOrLockdown(false) + userRepository.setSelectedUserInfo(mainUser) runCurrent() assertThat(isAvailable).isTrue() } @Test - fun isCommunalAvailable_whenStorageUnlock_true() = + fun isCommunalAvailable_storageLockedAndMainUser_false() = + testScope.runTest { + val isAvailable by collectLastValue(underTest.isCommunalAvailable) + assertThat(isAvailable).isFalse() + + keyguardRepository.setIsEncryptedOrLockdown(true) + userRepository.setSelectedUserInfo(mainUser) + runCurrent() + + assertThat(isAvailable).isFalse() + } + + @Test + fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() = testScope.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() keyguardRepository.setIsEncryptedOrLockdown(false) + userRepository.setSelectedUserInfo(secondaryUser) runCurrent() - assertThat(isAvailable).isTrue() + assertThat(isAvailable).isFalse() } @Test - fun updateAppWidgetHostActive_uponStorageUnlock_true() = + fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() = testScope.runTest { collectLastValue(underTest.isCommunalAvailable) assertThat(widgetRepository.isHostActive()).isFalse() keyguardRepository.setIsEncryptedOrLockdown(false) + userRepository.setSelectedUserInfo(mainUser) runCurrent() assertThat(widgetRepository.isHostActive()).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 161356d1bed2..352463f5a198 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.domain.interactor +import android.content.pm.UserInfo import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED @@ -30,9 +31,9 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope -import com.android.systemui.settings.UserTracker import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.mock +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -45,15 +46,17 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class CommunalTutorialInteractorTest : SysuiTestCase() { + @Mock lateinit var user: UserInfo + private val kosmos = testKosmos() private val testScope = kosmos.testScope - @Mock private lateinit var userTracker: UserTracker - private lateinit var underTest: CommunalTutorialInteractor private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository private lateinit var communalRepository: FakeCommunalRepository + private lateinit var communalInteractor: CommunalInteractor + private lateinit var userRepository: FakeUserRepository @Before fun setUp() { @@ -62,16 +65,17 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { keyguardRepository = kosmos.fakeKeyguardRepository communalTutorialRepository = kosmos.fakeCommunalTutorialRepository communalRepository = kosmos.fakeCommunalRepository + communalInteractor = kosmos.communalInteractor + userRepository = kosmos.fakeUserRepository underTest = kosmos.communalTutorialInteractor - - whenever(userTracker.userHandle).thenReturn(mock()) } @Test fun tutorialUnavailable_whenKeyguardNotVisible() = testScope.runTest { val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + setCommunalAvailable(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) keyguardRepository.setKeyguardShowing(false) assertThat(isTutorialAvailable).isFalse() @@ -81,6 +85,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { fun tutorialUnavailable_whenTutorialIsCompleted() = testScope.runTest { val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + setCommunalAvailable(true) keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) communalRepository.setIsCommunalHubShowing(false) @@ -89,9 +94,20 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { } @Test + fun tutorialUnavailable_whenCommunalNotAvailable() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + setCommunalAvailable(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) + keyguardRepository.setKeyguardShowing(true) + assertThat(isTutorialAvailable).isFalse() + } + + @Test fun tutorialAvailable_whenTutorialNotStarted() = testScope.runTest { val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + setCommunalAvailable(true) keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) communalRepository.setIsCommunalHubShowing(false) @@ -103,6 +119,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { fun tutorialAvailable_whenTutorialIsStarted() = testScope.runTest { val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + setCommunalAvailable(true) keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) communalRepository.setIsCommunalHubShowing(true) @@ -183,4 +200,16 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } + + private suspend fun setCommunalAvailable(available: Boolean) { + if (available) { + communalRepository.setIsCommunalEnabled(true) + keyguardRepository.setIsEncryptedOrLockdown(false) + whenever(user.isMain).thenReturn(true) + userRepository.setUserInfos(listOf(user)) + userRepository.setSelectedUserInfo(user) + } else { + keyguardRepository.setIsEncryptedOrLockdown(true) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt index 721fc4906aba..6b1b93777fbc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt @@ -21,14 +21,15 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -47,17 +48,16 @@ import org.mockito.MockitoAnnotations class CommunalLoggerStartableTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger - private lateinit var testScope: TestScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private lateinit var communalInteractor: CommunalInteractor private lateinit var underTest: CommunalLoggerStartable @Before fun setUp() { MockitoAnnotations.initMocks(this) - - val withDeps = CommunalInteractorFactory.create() - testScope = withDeps.testScope - communalInteractor = withDeps.communalInteractor + communalInteractor = kosmos.communalInteractor underTest = CommunalLoggerStartable( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 867a48dc3925..a2dec5ff8830 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -24,19 +24,21 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -59,8 +61,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var communalRepository: FakeCommunalRepository private lateinit var tutorialRepository: FakeCommunalTutorialRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository @@ -72,17 +72,14 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - val withDeps = CommunalInteractorFactory.create(testScope) - keyguardRepository = withDeps.keyguardRepository - communalRepository = withDeps.communalRepository - tutorialRepository = withDeps.tutorialRepository - widgetRepository = withDeps.widgetRepository - smartspaceRepository = withDeps.smartspaceRepository - mediaRepository = withDeps.mediaRepository + tutorialRepository = kosmos.fakeCommunalTutorialRepository + widgetRepository = kosmos.fakeCommunalWidgetRepository + smartspaceRepository = kosmos.fakeSmartspaceRepository + mediaRepository = kosmos.fakeCommunalMediaRepository underTest = CommunalEditModeViewModel( - withDeps.communalInteractor, + kosmos.communalInteractor, mediaHost, uiEventLogger, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 9ac21dd69aab..c814f3f1db6a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -17,31 +17,39 @@ package com.android.systemui.communal.view.viewmodel import android.app.smartspace.SmartspaceTarget +import android.content.pm.UserInfo import android.provider.Settings import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before @@ -57,16 +65,17 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class CommunalViewModelTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost + @Mock private lateinit var user: UserInfo - private lateinit var testScope: TestScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var communalRepository: FakeCommunalRepository private lateinit var tutorialRepository: FakeCommunalTutorialRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository - private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository + private lateinit var userRepository: FakeUserRepository private lateinit var underTest: CommunalViewModel @@ -74,22 +83,18 @@ class CommunalViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() - - val withDeps = CommunalInteractorFactory.create() - keyguardRepository = withDeps.keyguardRepository - communalRepository = withDeps.communalRepository - tutorialRepository = withDeps.tutorialRepository - widgetRepository = withDeps.widgetRepository - smartspaceRepository = withDeps.smartspaceRepository - mediaRepository = withDeps.mediaRepository - communalPrefsRepository = withDeps.communalPrefsRepository + keyguardRepository = kosmos.fakeKeyguardRepository + tutorialRepository = kosmos.fakeCommunalTutorialRepository + widgetRepository = kosmos.fakeCommunalWidgetRepository + smartspaceRepository = kosmos.fakeSmartspaceRepository + mediaRepository = kosmos.fakeCommunalMediaRepository + userRepository = kosmos.fakeUserRepository underTest = CommunalViewModel( testScope, - withDeps.communalInteractor, - withDeps.tutorialInteractor, + kosmos.communalInteractor, + kosmos.communalTutorialInteractor, mediaHost, ) } @@ -104,9 +109,11 @@ class CommunalViewModelTest : SysuiTestCase() { @Test fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() = testScope.runTest { - // Keyguard showing, and tutorial not started. + // Keyguard showing, storage unlocked, main user, and tutorial not started. keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) + keyguardRepository.setIsEncryptedOrLockdown(false) + setIsMainUser(true) tutorialRepository.setTutorialSettingState( Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED ) @@ -202,4 +209,10 @@ class CommunalViewModelTest : SysuiTestCase() { underTest.onHidePopupAfterDismissCta() assertThat(isPopupOnDismissCtaShowing).isEqualTo(false) } + + private suspend fun setIsMainUser(isMainUser: Boolean) { + whenever(user.isMain).thenReturn(isMainUser) + userRepository.setUserInfos(listOf(user)) + userRepository.setSelectedUserInfo(user) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 52305b1f5212..05b589126b08 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -49,10 +49,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository - private val trustRepository = kosmos.fakeTrustRepository - private val sceneInteractor = kosmos.sceneInteractor - private val authenticationInteractor = kosmos.authenticationInteractor + private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } + private val trustRepository by lazy { kosmos.fakeTrustRepository } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private lateinit var underTest: DeviceEntryInteractor @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 562f96c28b19..c14346899ede 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -93,7 +93,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Rule public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); - WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams(); + WindowManager.LayoutParams mWindowParams; @Mock IDreamOverlayCallback mDreamOverlayCallback; @@ -184,6 +184,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { when(mDreamOverlayContainerViewController.getContainerView()) .thenReturn(mDreamOverlayContainerView); + mWindowParams = new WindowManager.LayoutParams(); mService = new DreamOverlayService( mContext, mLifecycleOwner, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 6f62afc560fe..dc8b97abbfe8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -139,6 +139,18 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun topClippingBounds() = + testScope.runTest { + assertThat(underTest.topClippingBounds.value).isNull() + + underTest.topClippingBounds.value = 50 + assertThat(underTest.topClippingBounds.value).isEqualTo(50) + + underTest.topClippingBounds.value = 500 + assertThat(underTest.topClippingBounds.value).isEqualTo(500) + } + + @Test fun clockPosition() = testScope.runTest { assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 2c3afb1b40a9..0b320a28b419 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -54,15 +54,17 @@ class KeyguardInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardRepository - private val sceneInteractor = kosmos.sceneInteractor - private val commandQueue = FakeCommandQueue() + private val repository by lazy { kosmos.fakeKeyguardRepository } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val commandQueue by lazy { + FakeCommandQueue() + } private val bouncerRepository = FakeKeyguardBouncerRepository() private val shadeRepository = FakeShadeRepository() private val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone)) - private val underTest = + private val underTest by lazy { KeyguardInteractor( repository = repository, commandQueue = commandQueue, @@ -73,6 +75,7 @@ class KeyguardInteractorTest : SysuiTestCase() { shadeRepository = shadeRepository, sceneInteractorProvider = { sceneInteractor }, ) + } @Before fun setUp() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index 9bccf4e26e87..ce43d4e14cc1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -38,12 +38,12 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import org.mockito.Spy @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @@ -51,16 +51,19 @@ import org.mockito.Spy class LightRevealScrimInteractorTest : SysuiTestCase() { private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository() - @Spy private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository() + private val fakeLightRevealScrimRepository by lazy { + Mockito.spy(FakeLightRevealScrimRepository()) + } private val testScope = TestScope() - private val keyguardTransitionInteractor = + private val keyguardTransitionInteractor by lazy { KeyguardTransitionInteractorFactory.create( scope = testScope.backgroundScope, repository = fakeKeyguardTransitionRepository, ) .keyguardTransitionInteractor + } private lateinit var underTest: LightRevealScrimInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt index 9daf1860ebb8..199ffa6b87bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt @@ -20,11 +20,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.TransitionState @@ -36,6 +39,7 @@ import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -45,10 +49,18 @@ import org.junit.runner.RunWith class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository - private val biometricSettingsRepository = kosmos.biometricSettingsRepository - private val underTest = kosmos.alternateBouncerToAodTransitionViewModel + private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private lateinit var underTest: AlternateBouncerToAodTransitionViewModel + + @Before + fun setUp() { + keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + biometricSettingsRepository = kosmos.biometricSettingsRepository + underTest = kosmos.alternateBouncerToAodTransitionViewModel + } @Test fun deviceEntryParentViewAppear() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt index 3f7e0df427c6..d4438516a023 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt @@ -49,7 +49,9 @@ class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() { } private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val underTest = kosmos.alternateBouncerToGoneTransitionViewModel + private val underTest by lazy { + kosmos.alternateBouncerToGoneTransitionViewModel + } @Test fun deviceEntryParentViewDisappear() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt index c7ab52974852..ff41ea25f470 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt @@ -47,8 +47,8 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() } } private val testScope = kosmos.testScope - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val underTest = kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel + private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } + private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel } @Test fun deviceEntryParentViewDisappear() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index f1690dafe75a..89e29cf14451 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -20,10 +20,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -41,6 +43,7 @@ import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -50,9 +53,16 @@ import org.junit.runner.RunWith class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository - private val underTest = kosmos.dreamingToLockscreenTransitionViewModel + private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var underTest: DreamingToLockscreenTransitionViewModel + + @Before + fun setUp() { + keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + underTest = kosmos.dreamingToLockscreenTransitionViewModel + } @Test fun shortcutsAlpha_bothShortcutsReceiveLastValue() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index f763a6790b51..36b26a490fdb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -29,6 +30,7 @@ import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,8 +39,14 @@ import org.junit.runner.RunWith class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardTransitionRepository - private val underTest = kosmos.goneToDreamingTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var underTest: GoneToDreamingTransitionViewModel + + @Before + fun setUp() { + repository = kosmos.fakeKeyguardTransitionRepository + underTest = kosmos.goneToDreamingTransitionViewModel + } @Test fun runTest() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index fd2fd2f04cde..cceb76725615 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState @@ -54,11 +55,12 @@ class KeyguardRootViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository private val screenOffAnimationController = kosmos.screenOffAnimationController private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor private val dozeParameters = kosmos.dozeParameters - private val underTest = kosmos.keyguardRootViewModel + private val underTest by lazy { kosmos.keyguardRootViewModel } @Before fun setUp() { @@ -205,6 +207,19 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test + fun topClippingBounds() = + testScope.runTest { + val topClippingBounds by collectLastValue(underTest.topClippingBounds) + assertThat(topClippingBounds).isNull() + + keyguardRepository.topClippingBounds.value = 50 + assertThat(topClippingBounds).isEqualTo(50) + + keyguardRepository.topClippingBounds.value = 1000 + assertThat(topClippingBounds).isEqualTo(1000) + } + + @Test fun alpha_glanceableHubOpen_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index aa15d0befa80..4595fbfab0d7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -47,9 +47,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } - private val underTest = createLockscreenSceneViewModel() + private val underTest by lazy { + createLockscreenSceneViewModel() + } @Test fun upTransitionSceneKey_canSwipeToUnlock_gone() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 74025fd6e100..8f04ec3814eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -27,18 +27,22 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -51,10 +55,18 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardTransitionRepository - private val shadeRepository = kosmos.shadeRepository - private val keyguardRepository = kosmos.fakeKeyguardRepository - private val underTest = kosmos.lockscreenToDreamingTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var shadeRepository: ShadeRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var underTest: LockscreenToDreamingTransitionViewModel + + @Before + fun setUp() { + repository = kosmos.fakeKeyguardTransitionRepository + shadeRepository = kosmos.shadeRepository + keyguardRepository = kosmos.fakeKeyguardRepository + underTest = kosmos.lockscreenToDreamingTransitionViewModel + } @Test fun lockscreenFadeOut() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 6fcb0c11edad..b120f8776d9d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -22,12 +22,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState @@ -35,12 +38,14 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -52,11 +57,20 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardTransitionRepository - private val shadeRepository = kosmos.shadeRepository - private val keyguardRepository = kosmos.fakeKeyguardRepository - private val configurationRepository = kosmos.fakeConfigurationRepository - private val underTest = kosmos.lockscreenToOccludedTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var shadeRepository: ShadeRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var configurationRepository: FakeConfigurationRepository + private lateinit var underTest: LockscreenToOccludedTransitionViewModel + + @Before + fun setUp() { + repository = kosmos.fakeKeyguardTransitionRepository + shadeRepository = kosmos.shadeRepository + keyguardRepository = kosmos.fakeKeyguardRepository + configurationRepository = kosmos.fakeConfigurationRepository + underTest = kosmos.lockscreenToOccludedTransitionViewModel + } @Test fun lockscreenFadeOut() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 639114c15000..dddf6485d0f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -49,7 +49,9 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository val configurationRepository = kosmos.fakeConfigurationRepository - val underTest = kosmos.occludedToLockscreenTransitionViewModel + val underTest by lazy { + kosmos.occludedToLockscreenTransitionViewModel + } @Test fun lockscreenFadeIn() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 30b87bbbcf11..30ac34402ffd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -54,7 +54,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository val primaryBouncerInteractor = kosmos.primaryBouncerInteractor val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController - val underTest = kosmos.primaryBouncerToGoneTransitionViewModel + val underTest by lazy { + kosmos.primaryBouncerToGoneTransitionViewModel + } @Before fun setUp() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt index 96d57743e2ee..4207a9c27ad0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt @@ -54,7 +54,9 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { - private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } + private val kosmos by lazy { + Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } + } // Getter here so it can change when there is a managed profile. private val workTileAvailable: Boolean get() = hasManagedProfile() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt index 8ee6d2005350..d05e98faee22 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -36,8 +36,9 @@ import org.junit.runner.RunWith class ColorCorrectionTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig - private val subtitleArray = + private val subtitleArray by lazy { context.resources.getStringArray(R.array.tile_states_color_correction) + } // Using lazy (versus =) to make sure we override the right context -- see b/311612168 private val mapper by lazy { ColorCorrectionTileMapper( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt index a84b9fa32d85..da60c18dcfd7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt @@ -308,21 +308,27 @@ class CustomTileRepositoryTest : SysuiTestCase() { val TEST_COMPONENT = ComponentName("test.pkg", "test.cls") val TEST_USER_1 = UserHandle.of(1)!! - val TEST_TILE_1 = + val TEST_TILE_1 by lazy { Tile().apply { label = "test_tile_1" icon = Icon.createWithContentUri("file://test_1") } + } val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier) - val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) + val TEST_DEFAULTS_1 by lazy { + CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) + } val TEST_USER_2 = UserHandle.of(2)!! - val TEST_TILE_2 = + val TEST_TILE_2 by lazy { Tile().apply { label = "test_tile_2" icon = Icon.createWithContentUri("file://test_2") } + } val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier) - val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label) + val TEST_DEFAULTS_2 by lazy { + CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt index 20653ca18efc..995d6ac66137 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt @@ -180,11 +180,14 @@ class CustomTileInteractorTest : SysuiTestCase() { val TEST_COMPONENT = ComponentName("test.pkg", "test.cls") val TEST_USER = UserHandle.of(1)!! - val TEST_TILE = + val TEST_TILE by lazy { Tile().apply { label = "test_tile_1" icon = Icon.createWithContentUri("file://test_1") } - val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label) + } + val TEST_DEFAULTS by lazy { + CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt index f3c3579966fd..ccd7ed92b884 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt @@ -39,7 +39,9 @@ class ColorInversionTileMapperTest : SysuiTestCase() { private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec) - private val subtitleArray = context.resources.getStringArray(subtitleArrayId) + private val subtitleArray by lazy { + context.resources.getStringArray(subtitleArrayId) + } // Using lazy (versus =) to make sure we override the right context -- see b/311612168 private val mapper by lazy { ColorInversionTileMapper( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt index 7497ebdc2978..46e1609b02ad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt @@ -53,8 +53,9 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class UiModeNightTileDataInteractorTest : SysuiTestCase() { - private val configurationController: ConfigurationController = + private val configurationController: ConfigurationController by lazy { ConfigurationControllerImpl(context) + } private val batteryController = FakeBatteryController(LeakCheck()) private val locationController = FakeLocationController(LeakCheck()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index be523b813494..d7a794149869 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -52,7 +52,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 1cd764e01bad..1e5ebd0c2acd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -123,30 +123,32 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } private val testScope = kosmos.testScope - private val sceneContainerConfig = kosmos.sceneContainerConfig - private val sceneInteractor = kosmos.sceneInteractor - private val authenticationInteractor = kosmos.authenticationInteractor - private val deviceEntryInteractor = kosmos.deviceEntryInteractor - private val communalInteractor = kosmos.communalInteractor + private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } + private val communalInteractor by lazy { kosmos.communalInteractor } - private val transitionState = + private val transitionState by lazy { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey) ) - private val sceneContainerViewModel = + } + private val sceneContainerViewModel by lazy { SceneContainerViewModel( sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, ) .apply { setTransitionState(transitionState) } + } - private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor private lateinit var bouncerViewModel: BouncerViewModel - private val lockscreenSceneViewModel = + private val lockscreenSceneViewModel by lazy { LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, @@ -157,6 +159,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ), notifications = kosmos.notificationsPlaceholderViewModel, ) + } private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) @@ -179,8 +182,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel private lateinit var shadeSceneViewModel: ShadeSceneViewModel - private val keyguardInteractor = kosmos.keyguardInteractor - private val powerInteractor = kosmos.powerInteractor + private val keyguardInteractor by lazy { kosmos.keyguardInteractor } + private val powerInteractor by lazy { kosmos.powerInteractor } private var bouncerSceneJob: Job? = null diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index fc0df1288553..16cb6231a872 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -79,13 +79,13 @@ class SceneContainerStartableTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val sceneContainerFlags = kosmos.fakeSceneContainerFlags - private val authenticationInteractor = kosmos.authenticationInteractor - private val bouncerInteractor = kosmos.bouncerInteractor - private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository - private val deviceEntryInteractor = kosmos.deviceEntryInteractor - private val keyguardInteractor = kosmos.keyguardInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } + private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } + private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } + private val keyguardInteractor by lazy { kosmos.keyguardInteractor } private val sysUiState: SysUiState = mock() private val falsingCollector: FalsingCollector = mock() private val powerInteractor = PowerInteractorFactory.create().powerInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index ede453d85ee1..a16ce4325dcf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -41,7 +41,7 @@ import org.junit.runner.RunWith class SceneContainerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() - private val interactor = kosmos.sceneInteractor + private val interactor by lazy { kosmos.sceneInteractor } private lateinit var underTest: SceneContainerViewModel @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 51745023c5be..e9a2a3befb03 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations class ShadeHeaderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 251daffe2d91..5ef095fd8201 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -60,8 +60,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt index ef2046d85a14..ad4b98bdb3ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt @@ -69,7 +69,9 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() { private lateinit var controller: CommunalSmartspaceController // TODO(b/272811280): Remove usage of real view - private val fakeParent = FrameLayout(context) + private val fakeParent by lazy { + FrameLayout(context) + } /** * A class which implements SmartspaceView and extends View. This is mocked to provide the right diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt index e09385934991..3a386311223f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt @@ -73,8 +73,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent - @Spy - private var weatherSmartspaceView: SmartspaceView = TestView(context) + private val weatherSmartspaceView: SmartspaceView by lazy { + Mockito.spy(TestView(context)) + } @Mock private lateinit var targetFilter: SmartspaceTargetFilter @@ -88,8 +89,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @Mock private lateinit var precondition: SmartspacePrecondition - @Spy - private var smartspaceView: SmartspaceView = TestView(context) + private val smartspaceView: SmartspaceView by lazy { + Mockito.spy(TestView(context)) + } @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener @@ -100,7 +102,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { private lateinit var controller: DreamSmartspaceController // TODO(b/272811280): Remove usage of real view - private val fakeParent = FrameLayout(context) + private val fakeParent by lazy { + FrameLayout(context) + } /** * A class which implements SmartspaceView and extends View. This is mocked to provide the right diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index 607996d67d4f..6a2e31739c77 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -56,9 +56,9 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { } } private val testScope = kosmos.testScope - private val placeholderViewModel = kosmos.notificationsPlaceholderViewModel - private val appearanceViewModel = kosmos.notificationStackAppearanceViewModel - private val sceneInteractor = kosmos.sceneInteractor + private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel } + private val appearanceViewModel by lazy { kosmos.notificationStackAppearanceViewModel } + private val sceneInteractor by lazy { kosmos.sceneInteractor } @Test fun updateBounds() = diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml new file mode 100644 index 000000000000..045c19eb09dc --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z" + android:fillColor="#fff"/> + <path + android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z" + android:fillColor="#fff"/> + <path + android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z" + android:fillColor="#fff"/> + <path + android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z" + android:fillColor="#fff"/> + <path + android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z" + android:fillColor="#fff"/> + <path + android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z" + android:fillColor="#fff"/> + <path + android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z" + android:fillAlpha="0.3" + android:fillColor="#fff"/> + <path + android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z" + android:fillAlpha="0.3" + android:fillColor="#fff"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml new file mode 100644 index 000000000000..5e012ab7edbd --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z" + android:fillColor="#fff"/> + <path + android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z" + android:fillColor="#fff"/> + <path + android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z" + android:fillColor="#fff"/> + <path + android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z" + android:fillColor="#fff"/> + <path + android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z" + android:fillColor="#fff"/> + <path + android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z" + android:fillColor="#fff"/> + <path + android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z" + android:fillAlpha="0.3" + android:fillColor="#fff"/> + <path + android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z" + android:fillColor="#fff"/> +</vector> + diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml new file mode 100644 index 000000000000..d8a9a703260d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z" + android:fillColor="#fff"/> + <path + android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z" + android:fillColor="#fff"/> + <path + android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z" + android:fillColor="#fff"/> + <path + android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z" + android:fillColor="#fff"/> + <path + android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z" + android:fillColor="#fff"/> + <path + android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z" + android:fillColor="#fff"/> + <path + android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z" + android:fillColor="#fff"/> + <path + android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z" + android:fillColor="#fff"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml new file mode 100644 index 000000000000..dec9930959a0 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + > + <path + android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z" + android:fillColor="#fff"/> + <path + android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z" + android:fillColor="#fff"/> + <path + android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z" + android:fillColor="#fff"/> + <path + android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z" + android:fillColor="#fff"/> + <path + android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z" + android:fillColor="#fff"/> + <path + android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z" + android:fillColor="#fff"/> +</vector> diff --git a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml new file mode 100644 index 000000000000..ee4d05c3bda5 --- /dev/null +++ b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Base layout that provides a single bindable icon_view id image view --> +<com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical" + > + + <ImageView + android:id="@+id/icon_view" + android:layout_height="@dimen/status_bar_bindable_icon_size" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical" + android:padding="@dimen/status_bar_bindable_icon_padding" + android:scaleType="fitCenter" + /> + +</com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView> diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml index 3be99939ba0f..156c98333f3a 100644 --- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml +++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml @@ -37,6 +37,7 @@ android:layout_height="wrap_content" android:background="@drawable/qs_customizer_toolbar" android:navigationContentDescription="@*android:string/action_bar_up_description" + android:titleTextAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title" style="@style/QSCustomizeToolbar" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ee2a1ceab2b7..c630a7ff09b6 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -151,6 +151,8 @@ <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen> <!-- Original dp height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> + <dimen name="status_bar_bindable_icon_size">20sp</dimen> + <dimen name="status_bar_bindable_icon_padding">2sp</dimen> <!-- Default horizontal drawable padding for status bar icons. --> <dimen name="status_bar_horizontal_padding">2.5sp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 033f93b260ab..ad303171d22e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -477,9 +477,13 @@ public class KeyguardClockSwitch extends RelativeLayout { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardClockSwitch:"); pw.println(" mSmallClockFrame = " + mSmallClockFrame); - pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha()); + if (mSmallClockFrame != null) { + pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha()); + } pw.println(" mLargeClockFrame = " + mLargeClockFrame); - pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha()); + if (mLargeClockFrame != null) { + pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha()); + } pw.println(" mStatusArea = " + mStatusArea); pw.println(" mDisplayedClockSize = " + mDisplayedClockSize); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 6869bbaedcf3..97999cc19dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -240,7 +240,9 @@ class MenuViewLayer extends FrameLayout implements mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> { if (Flags.floatingMenuDragToHide()) { dismissNotification(); - undo(); + if (newTargetFeatures.size() > 0) { + undo(); + } } else { if (newTargetFeatures.size() < 1) { return; diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 92d01dbe3c3e..44b0383e12c6 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.smartspace.data.repository.SmartspaceRepository +import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -62,6 +63,7 @@ constructor( private val communalPrefsRepository: CommunalPrefsRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, + userRepository: UserRepository, keyguardInteractor: KeyguardInteractor, private val appWidgetHost: CommunalAppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter @@ -75,7 +77,14 @@ constructor( val isCommunalAvailable: StateFlow<Boolean> = flowOf(isCommunalEnabled) .flatMapLatest { enabled -> - if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false) + if (enabled) + combine( + keyguardInteractor.isEncryptedOrLockdown, + userRepository.selectedUserInfo, + ) { isEncryptedOrLockdown, selectedUserInfo -> + !isEncryptedOrLockdown && selectedUserInfo.isMain + } + else flowOf(false) } .distinctUntilChanged() .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) } @@ -263,6 +272,12 @@ constructor( companion object { /** + * The user activity timeout which should be used when the communal hub is opened. A value + * of -1 means that the user's chosen screen timeout will be used instead. + */ + const val AWAKE_INTERVAL_MS = -1 + + /** * Calculates the content size dynamically based on the total number of contents of that * type. * diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt index 5ca89f28f1fc..4e5be9b8aa5e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt @@ -45,14 +45,17 @@ constructor( private val communalTutorialRepository: CommunalTutorialRepository, keyguardInteractor: KeyguardInteractor, private val communalRepository: CommunalRepository, + communalInteractor: CommunalInteractor, ) { /** An observable for whether the tutorial is available. */ val isTutorialAvailable: Flow<Boolean> = combine( + communalInteractor.isCommunalAvailable, keyguardInteractor.isKeyguardVisible, communalTutorialRepository.tutorialSettingState, - ) { isKeyguardVisible, tutorialSettingState -> - isKeyguardVisible && + ) { isCommunalAvailable, isKeyguardVisible, tutorialSettingState -> + isCommunalAvailable && + isKeyguardVisible && tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 2bb9d0e52757..a909383f4a2c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -74,6 +74,7 @@ constructor( // before the MediaHierarchyManager attempts to move the UMO to the hub. with(mediaHost) { expansion = MediaHostState.EXPANDED + expandedMatchesParentHeight = true showsOnlyActiveMedia = false falsingProtectionNeeded = false init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index d012d24b767e..14371949c9c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -132,6 +132,9 @@ interface KeyguardRepository { */ val isDozing: StateFlow<Boolean> + /** Keyguard can be clipped at the top as the shade is dragged */ + val topClippingBounds: MutableStateFlow<Int?> + /** * Observable for whether the device is dreaming. * @@ -326,6 +329,8 @@ constructor( private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() + override val topClippingBounds = MutableStateFlow<Int?>(null) + override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 6170356d4a90..91747e0f69a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -171,6 +171,14 @@ constructor( /** Whether the keyguard is going away. */ val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway + /** Keyguard can be clipped at the top as the shade is dragged */ + val topClippingBounds: Flow<Int?> = + combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) { + _, + topClippingBounds -> + topClippingBounds + } + /** Last point that [KeyguardRootView] view was tapped */ val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow() @@ -328,6 +336,10 @@ constructor( repository.keyguardDoneAnimationsFinished() } + fun setTopClippingBounds(top: Int?) { + repository.topClippingBounds.value = top + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 2aebd99e3664..48092c6374e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.annotation.DrawableRes import android.annotation.SuppressLint import android.graphics.Point +import android.graphics.Rect import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener @@ -158,6 +159,23 @@ object KeyguardRootViewBinder { } launch { + val clipBounds = Rect() + viewModel.topClippingBounds.collect { clipTop -> + if (clipTop == null) { + view.setClipBounds(null) + } else { + clipBounds.apply { + top = clipTop + left = view.getLeft() + right = view.getRight() + bottom = view.getBottom() + } + view.setClipBounds(clipBounds) + } + } + } + + launch { viewModel.lockscreenStateAlpha.collect { alpha -> childViews[statusViewId]?.alpha = alpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 5d36da9b9df1..ea6670267270 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -80,6 +80,12 @@ constructor( val notificationBounds: StateFlow<NotificationContainerBounds> = keyguardInteractor.notificationContainerBounds + /** + * The keyguard root view can be clipped as the shade is pulled down, typically only for + * non-split shade cases. + */ + val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds + /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = merge( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt index 631a0b8471b8..437218f9f440 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt @@ -228,6 +228,14 @@ constructor( } } + override var expandedMatchesParentHeight: Boolean = false + set(value) { + if (value != field) { + field = value + changedListener?.invoke() + } + } + override var squishFraction: Float = 1.0f set(value) { if (!value.equals(field)) { @@ -282,6 +290,7 @@ constructor( override fun copy(): MediaHostState { val mediaHostState = MediaHostStateHolder() mediaHostState.expansion = expansion + mediaHostState.expandedMatchesParentHeight = expandedMatchesParentHeight mediaHostState.squishFraction = squishFraction mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() @@ -360,6 +369,12 @@ interface MediaHostState { */ var expansion: Float + /** + * If true, the [EXPANDED] layout should stretch to match the height of its parent container, + * rather than having a fixed height. + */ + var expandedMatchesParentHeight: Boolean + /** Fraction of the height animation. */ var squishFraction: Float diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index be9393655c5d..962764c028fc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.res.Configuration import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT import com.android.app.tracing.traceSection import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaViewHolder @@ -152,18 +153,11 @@ constructor( lastOrientation = newOrientation // Update the height of media controls for the expanded layout. it is needed // for large screen devices. - val backgroundIds = - if (type == TYPE.PLAYER) { - MediaViewHolder.backgroundIds - } else { - setOf(RecommendationViewHolder.backgroundId) - } - backgroundIds.forEach { id -> - expandedLayout.getConstraint(id).layout.mHeight = - context.resources.getDimensionPixelSize( - R.dimen.qs_media_session_height_expanded - ) - } + setBackgroundHeights( + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + ) } if (this@MediaViewController::configurationChangeListener.isInitialized) { configurationChangeListener.invoke() @@ -276,6 +270,17 @@ constructor( private fun constraintSetForExpansion(expansion: Float): ConstraintSet = if (expansion > 0) expandedLayout else collapsedLayout + /** Set the height of UMO background constraints. */ + private fun setBackgroundHeights(height: Int) { + val backgroundIds = + if (type == TYPE.PLAYER) { + MediaViewHolder.backgroundIds + } else { + setOf(RecommendationViewHolder.backgroundId) + } + backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height } + } + /** * Set the views to be showing/hidden based on the [isGutsVisible] for a given * [TransitionViewState]. @@ -454,6 +459,18 @@ constructor( } // Let's create a new measurement if (state.expansion == 0.0f || state.expansion == 1.0f) { + if (state.expansion == 1.0f) { + val height = + if (state.expandedMatchesParentHeight) { + MATCH_CONSTRAINT + } else { + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + } + setBackgroundHeights(height) + } + result = transitionLayout!!.calculateViewState( state.measurementInput!!, diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index a103566400a6..07705f361881 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -58,6 +58,7 @@ public class QSCustomizer extends LinearLayout { private final RecyclerView mRecyclerView; private boolean mCustomizing; private QSContainerController mQsContainerController; + private final Toolbar mToolbar; private QS mQs; private int mX; private int mY; @@ -69,15 +70,15 @@ public class QSCustomizer extends LinearLayout { LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); mClipper = new QSDetailClipper(findViewById(R.id.customize_container)); - Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar); + mToolbar = findViewById(com.android.internal.R.id.action_bar); TypedValue value = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); - toolbar.setNavigationIcon( + mToolbar.setNavigationIcon( getResources().getDrawable(value.resourceId, mContext.getTheme())); - toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) + mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toolbar.setTitle(R.string.qs_edit); + mToolbar.setTitle(R.string.qs_edit); mRecyclerView = findViewById(android.R.id.list); mTransparentView = findViewById(R.id.customizer_transparent_view); DefaultItemAnimator animator = new DefaultItemAnimator(); @@ -184,6 +185,14 @@ public class QSCustomizer extends LinearLayout { return isShown; } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mToolbar.setTitleTextAppearance(mContext, + android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title); + updateToolbarMenuFontSize(); + } + void setCustomizing(boolean customizing) { mCustomizing = customizing; if (mQs != null) { @@ -269,4 +278,11 @@ public class QSCustomizer extends LinearLayout { lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); mTransparentView.setLayoutParams(lp); } + + private void updateToolbarMenuFontSize() { + // Clearing and re-adding the toolbar action force updates the font size + mToolbar.getMenu().clear(); + mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 456520051f58..c5eeb2f87d3a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -249,6 +249,10 @@ open class QSTileViewImpl @JvmOverloads constructor( height = iconSize marginEnd = endMargin } + + background = createTileBackground() + setColor(backgroundColor) + setOverlayColor(backgroundOverlayColor) } private fun createAndAddLabels() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 60feb82bf4aa..00534743588c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -118,6 +119,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final Lazy<SelectedUserInteractor> mUserInteractor; private final Lazy<ShadeInteractor> mShadeInteractorLazy; private final SceneContainerFlags mSceneContainerFlags; + private final Lazy<CommunalInteractor> mCommunalInteractor; private ViewGroup mWindowRootView; private LayoutParams mLp; private boolean mHasTopUi; @@ -165,7 +167,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW ShadeWindowLogger logger, Lazy<SelectedUserInteractor> userInteractor, UserTracker userTracker, - SceneContainerFlags sceneContainerFlags) { + SceneContainerFlags sceneContainerFlags, + Lazy<CommunalInteractor> communalInteractor) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; mWindowManager = windowManager; @@ -184,6 +187,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mAuthController = authController; mUserInteractor = userInteractor; mSceneContainerFlags = sceneContainerFlags; + mCommunalInteractor = communalInteractor; mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() .getInteger(R.integer.config_lockScreenDisplayTimeout); @@ -325,6 +329,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mShadeInteractorLazy.get().isQsExpanded(), this::onQsExpansionChanged ); + collectFlow( + mWindowRootView, + mCommunalInteractor.get().isCommunalShowing(), + this::onCommunalShowingChanged + ); } @Override @@ -501,14 +510,21 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } private void applyUserActivityTimeout(NotificationShadeWindowState state) { - if (state.isKeyguardShowingAndNotOccluded() + final Boolean communalShowing = state.isCommunalShowingAndNotOccluded(); + final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded(); + long timeout = -1; + if ((communalShowing || keyguardShowing) && state.statusBarState == StatusBarState.KEYGUARD && !state.qsExpanded) { - mLpChanged.userActivityTimeout = state.bouncerShowing - ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout; - } else { - mLpChanged.userActivityTimeout = -1; + if (state.bouncerShowing) { + timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS; + } else if (communalShowing) { + timeout = CommunalInteractor.AWAKE_INTERVAL_MS; + } else if (keyguardShowing) { + timeout = mLockScreenDisplayTimeout; + } } + mLpChanged.userActivityTimeout = timeout; } private void applyInputFeatures(NotificationShadeWindowState state) { @@ -607,7 +623,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.forcePluginOpen, state.dozing, state.scrimsVisibility, - state.backgroundBlurRadius + state.backgroundBlurRadius, + state.communalShowing ); } @@ -731,6 +748,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW apply(mCurrentState); } + @VisibleForTesting + void onCommunalShowingChanged(Boolean showing) { + mCurrentState.communalShowing = showing; + apply(mCurrentState); + } + @Override public void setForceUserActivity(boolean forceUserActivity) { mCurrentState.forceUserActivity = forceUserActivity; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index 0b20170834d8..f9c9d83e03aa 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -58,12 +58,17 @@ class NotificationShadeWindowState( @JvmField var dreaming: Boolean = false, @JvmField var scrimsVisibility: Int = 0, @JvmField var backgroundBlurRadius: Int = 0, + @JvmField var communalShowing: Boolean = false, ) { fun isKeyguardShowingAndNotOccluded(): Boolean { return keyguardShowing && !keyguardOccluded } + fun isCommunalShowingAndNotOccluded(): Boolean { + return communalShowing && !keyguardOccluded + } + /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ val asStringList: List<String> by lazy { listOf( @@ -93,7 +98,8 @@ class NotificationShadeWindowState( forcePluginOpen.toString(), dozing.toString(), scrimsVisibility.toString(), - backgroundBlurRadius.toString() + backgroundBlurRadius.toString(), + communalShowing.toString(), ) } @@ -134,6 +140,7 @@ class NotificationShadeWindowState( dozing: Boolean, scrimsVisibility: Int, backgroundBlurRadius: Int, + communalShowing: Boolean, ) { buffer.advance().apply { this.keyguardShowing = keyguardShowing @@ -165,6 +172,7 @@ class NotificationShadeWindowState( this.dozing = dozing this.scrimsVisibility = scrimsVisibility this.backgroundBlurRadius = backgroundBlurRadius + this.communalShowing = communalShowing } } @@ -209,7 +217,8 @@ class NotificationShadeWindowState( "forcePluginOpen", "dozing", "scrimsVisibility", - "backgroundBlurRadius" + "backgroundBlurRadius", + "communalShowing" ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 46806e66cb8c..54b6ad71e734 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -790,7 +790,7 @@ private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R { /** Mutates the HeadsUp state of notifications. */ private interface HunMutator { - fun updateNotification(key: String, alert: Boolean) + fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) fun removeNotification(key: String, releaseImmediately: Boolean) } @@ -801,8 +801,8 @@ private interface HunMutator { private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator { private val deferred = mutableListOf<Pair<String, Boolean>>() - override fun updateNotification(key: String, alert: Boolean) { - headsUpManager.updateNotification(key, alert) + override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) { + headsUpManager.updateNotification(key, shouldHeadsUpAgain) } override fun removeNotification(key: String, releaseImmediately: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt index 380cdadd1361..ae4ba27775b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.screenshareNotificationHiding import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import dagger.Binds import dagger.Module @@ -55,6 +57,8 @@ class SensitiveContentCoordinatorImpl @Inject constructor( private val statusBarStateController: StatusBarStateController, private val keyguardStateController: KeyguardStateController, private val selectedUserInteractor: SelectedUserInteractor, + private val sensitiveNotificationProtectionController: + SensitiveNotificationProtectionController, ) : Invalidator("SensitiveContentInvalidator"), SensitiveContentCoordinator, DynamicPrivacyController.Listener, @@ -82,10 +86,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor( return } + val isSensitiveContentProtectionActive = screenshareNotificationHiding() && + sensitiveNotificationProtectionController.isSensitiveStateActive val currentUserId = lockscreenUserManager.currentUserId val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) - val deviceSensitive = devicePublic && - !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId) + val deviceSensitive = (devicePublic && + !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) || + isSensitiveContentProtectionActive val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { val notifUserId = entry.sbn.user.identifier @@ -105,9 +112,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor( else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId) } } + + val shouldProtectNotification = screenshareNotificationHiding() && + sensitiveNotificationProtectionController.shouldProtectNotification(entry) + val needsRedaction = lockscreenUserManager.needsRedaction(entry) val isSensitive = userPublic && needsRedaction - entry.setSensitive(isSensitive, deviceSensitive) + entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 0bc8e682c891..f375ebce2de3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -187,7 +187,7 @@ object NotificationIconContainerViewBinder { configuration.getDimensionPixelSize(RInternal.dimen.status_bar_icon_size_sp) val iconHorizontalPaddingFlow: Flow<Int> = configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin) - val layoutParams: Flow<FrameLayout.LayoutParams> = + val layoutParams: StateFlow<FrameLayout.LayoutParams> = combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) { iconSize, iconHPadding, @@ -206,7 +206,7 @@ object NotificationIconContainerViewBinder { private suspend fun Flow<NotificationIconsViewData>.bindIcons( view: NotificationIconContainer, - layoutParams: Flow<FrameLayout.LayoutParams>, + layoutParams: StateFlow<FrameLayout.LayoutParams>, notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit, @@ -259,7 +259,7 @@ object NotificationIconContainerViewBinder { // added again. removeTransientView(sbiv) } - view.addView(sbiv) + view.addView(sbiv, layoutParams.value) boundViewsByNotifKey.remove(notifKey)?.second?.cancel() boundViewsByNotifKey[notifKey] = Pair( @@ -267,7 +267,9 @@ object NotificationIconContainerViewBinder { launch { launch { layoutParams.collectTracingEach("SBIV#bindLayoutParams") { - sbiv.layoutParams = it + if (it != sbiv.layoutParams) { + sbiv.layoutParams = it + } } } bindIcon(notifKey, sbiv) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt new file mode 100644 index 000000000000..a21dd9bb9579 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the notification avalanche suppression flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationAvalancheSuppression { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationAvalancheSuppression() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 6a66bb74f16d..9a8cc0ae33cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -22,6 +22,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N import static com.android.app.animation.Interpolators.STANDARD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.Flags.screenshareNotificationHiding; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener; @@ -135,6 +136,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; @@ -218,6 +220,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final SecureSettings mSecureSettings; private final NotificationDismissibilityProvider mDismissibilityProvider; private final ActivityStarter mActivityStarter; + private final SensitiveNotificationProtectionController + mSensitiveNotificationProtectionController; private View mLongPressedView; @@ -295,6 +299,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; + private final Runnable mSensitiveStateChangedListener = new Runnable() { + @Override + public void run() { + // Animate false to protect against screen recording capturing content + // during the animation + updateSensitivenessWithAnimation(false); + } + }; + private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { if (mView.isExpanded()) { // The bottom might change because we're using the final actual height of the view @@ -399,7 +412,20 @@ public class NotificationStackScrollLayoutController implements Dumpable { } private void updateSensitivenessWithAnimation(boolean animate) { - mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode()); + Trace.beginSection("NSSLC.updateSensitivenessWithAnimation"); + if (screenshareNotificationHiding()) { + boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode(); + boolean isSensitiveContentProtectionActive = + mSensitiveNotificationProtectionController.isSensitiveStateActive(); + boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive; + + // Only animate if in a non-sensitive state (not screen sharing) + boolean shouldAnimate = animate && !isSensitiveContentProtectionActive; + mView.updateSensitiveness(shouldAnimate, isSensitive); + } else { + mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode()); + } + Trace.endSection(); } /** @@ -708,7 +734,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { SecureSettings secureSettings, NotificationDismissibilityProvider dismissibilityProvider, ActivityStarter activityStarter, - SplitShadeStateController splitShadeStateController) { + SplitShadeStateController splitShadeStateController, + SensitiveNotificationProtectionController sensitiveNotificationProtectionController) { mView = view; mKeyguardTransitionRepo = keyguardTransitionRepo; mViewBinder = viewBinder; @@ -756,6 +783,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mSecureSettings = secureSettings; mDismissibilityProvider = dismissibilityProvider; mActivityStarter = activityStarter; + mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController; mView.passSplitShadeStateController(splitShadeStateController); mDumpManager.registerDumpable(this); updateResources(); @@ -860,6 +888,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); mDeviceProvisionedListener.onDeviceProvisionedChanged(); + if (screenshareNotificationHiding()) { + mSensitiveNotificationProtectionController + .registerSensitiveStateListener(mSensitiveStateChangedListener); + } + if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index ae04eaf49b65..459b368b5ac9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -217,6 +218,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private final ScreenOffAnimationController mScreenOffAnimationController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final KeyguardInteractor mKeyguardInteractor; private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; @@ -311,6 +313,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel, KeyguardTransitionInteractor keyguardTransitionInteractor, + KeyguardInteractor keyguardInteractor, WallpaperRepository wallpaperRepository, @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator) { @@ -357,6 +360,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mKeyguardInteractor = keyguardInteractor; mWallpaperRepository = wallpaperRepository; mMainDispatcher = mainDispatcher; } @@ -759,7 +763,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // see: b/186644628 mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom); mScrimBehind.setBottomEdgePosition((int) top); + mKeyguardInteractor.setTopClippingBounds((int) top); } else { + mKeyguardInteractor.setTopClippingBounds(null); mNotificationsScrim.setDrawableBounds(left, top, right, bottom); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt new file mode 100644 index 000000000000..252945f1ed6a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.dagger + +import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository +import javax.inject.Qualifier + +/** Detailed [DeviceBasedSatelliteRepository] logs */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class OemSatelliteInputLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index e309c32df64e..2b90e649a154 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -265,6 +265,13 @@ abstract class StatusBarPipelineModule { return factory.create("VerboseMobileViewLog", 100) } + @Provides + @SysUISingleton + @OemSatelliteInputLog + fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer { + return factory.create("DeviceBasedSatelliteInputLog", 32) + } + const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON = "FirstMobileSubShowingNetworkTypeIcon" } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt index e3c3139f6906..8400fb08e147 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.icons.shared import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon +import com.android.systemui.statusbar.pipeline.satellite.ui.DeviceBasedSatelliteBindableIcon import javax.inject.Inject /** @@ -38,11 +39,12 @@ interface BindableIconsRegistry { class BindableIconsRegistryImpl @Inject constructor( -/** Bindables go here */ + /** Bindables go here */ + oemSatellite: DeviceBasedSatelliteBindableIcon ) : BindableIconsRegistry { /** * Adding the injected bindables to this list will get them registered with * StatusBarIconController */ - override val bindableIcons: List<BindableIcon> = listOf() + override val bindableIcons: List<BindableIcon> = listOf(oemSatellite) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index bc38b538f8c0..a608be39f7f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -72,6 +72,9 @@ interface MobileConnectionRepository { */ val isInService: StateFlow<Boolean> + /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */ + val isNonTerrestrial: StateFlow<Boolean> + /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */ val isGsm: StateFlow<Boolean> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index b2a773375cd5..6de7a00b5a10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullM import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_NTN import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING @@ -109,6 +110,17 @@ class DemoMobileConnectionRepository( ) .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value) + private val _isNonTerrestrial = MutableStateFlow(false) + override val isNonTerrestrial = + _isNonTerrestrial + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + columnName = COL_IS_NTN, + _isNonTerrestrial.value + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), _isNonTerrestrial.value) + private val _isGsm = MutableStateFlow(false) override val isGsm = _isGsm @@ -227,6 +239,7 @@ class DemoMobileConnectionRepository( (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel() _carrierNetworkChangeActive.value = event.carrierNetworkChange _resolvedNetworkType.value = resolvedNetworkType + _isNonTerrestrial.value = event.ntn isAllowedDuringAirplaneMode.value = false hasPrioritizedNetworkCapabilities.value = event.slice diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt index 4cd877eb1a14..11a61a9fd319 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt @@ -77,6 +77,7 @@ constructor( val roaming = getString("roam") == "show" val name = getString("networkname") ?: "demo mode" val slice = getString("slice").toBoolean() + val ntn = getString("ntn").toBoolean() return Mobile( level = level, @@ -89,6 +90,7 @@ constructor( roaming = roaming, name = name, slice = slice, + ntn = ntn, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt index 0aa95f8821cc..4836abe73f86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt @@ -37,6 +37,7 @@ sealed interface FakeNetworkEventModel { val roaming: Boolean, val name: String, val slice: Boolean = false, + val ntn: Boolean = false, ) : FakeNetworkEventModel data class MobileDisabled( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index e5a5695d3655..f8858c5037ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -168,6 +168,7 @@ class CarrierMergedConnectionRepository( override val isEmergencyOnly = MutableStateFlow(false).asStateFlow() override val operatorAlphaShort = MutableStateFlow(null).asStateFlow() override val isInService = MutableStateFlow(true).asStateFlow() + override val isNonTerrestrial = MutableStateFlow(false).asStateFlow() override val isGsm = MutableStateFlow(false).asStateFlow() override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index 48bf7ac60ba7..a1241965de7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -175,6 +175,21 @@ class FullMobileConnectionRepository( ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value) + override val isNonTerrestrial = + activeRepo + .flatMapLatest { it.isNonTerrestrial } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + columnName = COL_IS_NTN, + activeRepo.value.isNonTerrestrial.value + ) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + activeRepo.value.isNonTerrestrial.value + ) + override val isGsm = activeRepo .flatMapLatest { it.isGsm } @@ -366,6 +381,7 @@ class FullMobileConnectionRepository( const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive" const val COL_CDMA_LEVEL = "cdmaLevel" const val COL_EMERGENCY = "emergencyOnly" + const val COL_IS_NTN = "isNtn" const val COL_IS_GSM = "isGsm" const val COL_IS_IN_SERVICE = "isInService" const val COL_OPERATOR = "operatorName" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index f44401ba4702..77fd6bef8a33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -227,6 +227,12 @@ class MobileConnectionRepositoryImpl( .map { Utils.isInService(it.serviceState) } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val isNonTerrestrial = + callbackEvents + .mapNotNull { it.onServiceStateChanged } + .map { it.serviceState.isUsingNonTerrestrialNetwork } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val isGsm = callbackEvents .mapNotNull { it.onSignalStrengthChanged } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index de46a5ed99d6..0e6775685611 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -19,11 +19,18 @@ package com.android.systemui.statusbar.pipeline.satellite.data.prod import android.os.OutcomeReceiver import android.telephony.satellite.NtnSignalStrengthCallback import android.telephony.satellite.SatelliteManager +import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS import android.telephony.satellite.SatelliteModemStateCallback +import androidx.annotation.VisibleForTesting import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter +import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported @@ -62,8 +69,10 @@ private typealias SupportedSatelliteManager = SatelliteManager /** * "Supported" here means supported by the device. The value of this should be stable during the * process lifetime. + * + * @VisibleForTesting */ -private sealed interface SatelliteSupport { +sealed interface SatelliteSupport { /** Not yet fetched */ data object Unknown : SatelliteSupport @@ -123,6 +132,7 @@ constructor( satelliteManagerOpt: Optional<SatelliteManager>, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, + @OemSatelliteInputLog private val logBuffer: LogBuffer, private val systemClock: SystemClock, ) : DeviceBasedSatelliteRepository { @@ -132,7 +142,8 @@ constructor( // Some calls into satellite manager will throw exceptions if it is not supported. // This is never expected to change after boot, but may need to be retried in some cases - private val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown) + @get:VisibleForTesting + val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown) init { satelliteManager = satelliteManagerOpt.getOrNull() @@ -145,6 +156,11 @@ constructor( ensureMinUptime(systemClock, MIN_UPTIME) satelliteSupport.value = satelliteManager.checkSatelliteSupported() + logBuffer.i( + { str1 = satelliteSupport.value.toString() }, + { "Checked for system support. support=$str1" }, + ) + // We only need to check location availability if this mode is supported if (satelliteSupport.value is Supported) { isSatelliteAllowedForCurrentLocation.subscriptionCount @@ -159,6 +175,9 @@ constructor( * connection might cause more frequent checks. */ while (true) { + logBuffer.i { + "requestIsSatelliteCommunicationAllowedForCurrentLocation" + } checkIsSatelliteAllowed() delay(POLLING_INTERVAL_MS) } @@ -167,6 +186,8 @@ constructor( } } } else { + logBuffer.i { "Satellite manager is null" } + satelliteSupport.value = NotSupported } } @@ -181,12 +202,21 @@ constructor( private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> = conflatedCallbackFlow { val cb = SatelliteModemStateCallback { state -> + logBuffer.i({ int1 = state }) { "onSatelliteModemStateChanged: state=$int1" } trySend(SatelliteConnectionState.fromModemState(state)) } - sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb) + var registered = false - awaitClose { sm.unregisterForSatelliteModemStateChanged(cb) } + try { + val res = + sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb) + registered = res == SATELLITE_RESULT_SUCCESS + } catch (e: Exception) { + logBuffer.e("error registering for modem state", e) + } + + awaitClose { if (registered) sm.unregisterForSatelliteModemStateChanged(cb) } } .flowOn(bgDispatcher) @@ -197,12 +227,21 @@ constructor( private fun signalStrengthFlow(sm: SupportedSatelliteManager) = conflatedCallbackFlow { val cb = NtnSignalStrengthCallback { signalStrength -> + logBuffer.i({ int1 = signalStrength.level }) { + "onNtnSignalStrengthChanged: level=$int1" + } trySend(signalStrength.level) } - sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb) + var registered = false + try { + sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb) + registered = true + } catch (e: Exception) { + logBuffer.e("error registering for signal strength", e) + } - awaitClose { sm.unregisterForNtnSignalStrengthChanged(cb) } + awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) } } .flowOn(bgDispatcher) @@ -213,11 +252,15 @@ constructor( bgDispatcher.asExecutor(), object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> { override fun onError(e: SatelliteManager.SatelliteException) { - android.util.Log.e(TAG, "Found exception when checking for satellite: ", e) + logBuffer.e( + "Found exception when checking availability", + e, + ) isSatelliteAllowedForCurrentLocation.value = false } override fun onResult(allowed: Boolean) { + logBuffer.i { allowed.toString() } isSatelliteAllowedForCurrentLocation.value = allowed } } @@ -239,12 +282,27 @@ constructor( } override fun onError(error: SatelliteManager.SatelliteException) { + logBuffer.e( + "Exception when checking for satellite support. " + + "Assuming it is not supported for this device.", + error, + ) + // Assume that an error means it's not supported continuation.resume(NotSupported) } } - requestIsSatelliteSupported(bgDispatcher.asExecutor(), cb) + try { + requestIsSatelliteSupported(bgDispatcher.asExecutor(), cb) + } catch (error: Exception) { + logBuffer.e( + "Exception when checking for satellite support. " + + "Assuming it is not supported for this device.", + error, + ) + continuation.resume(NotSupported) + } } companion object { @@ -264,5 +322,19 @@ constructor( delay(timeTilMinUptime) } } + + /** A couple of convenience logging methods rather than a whole class */ + private fun LogBuffer.i( + initializer: MessageInitializer = {}, + printer: MessagePrinter, + ) = this.log(TAG, LogLevel.INFO, initializer, printer) + + private fun LogBuffer.e(message: String, exception: Throwable? = null) = + this.log( + tag = TAG, + level = LogLevel.ERROR, + message = message, + exception = exception, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt new file mode 100644 index 000000000000..f5d0f6b8f07c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.ui + +import android.content.Context +import com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon +import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator +import com.android.systemui.statusbar.pipeline.satellite.ui.binder.DeviceBasedSatelliteIconBinder +import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel +import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView +import javax.inject.Inject + +@SysUISingleton +class DeviceBasedSatelliteBindableIcon +@Inject +constructor( + context: Context, + viewModel: DeviceBasedSatelliteViewModel, +) : BindableIcon { + override val slot: String = + context.getString(com.android.internal.R.string.status_bar_oem_satellite) + + override val initializer = ModernStatusBarViewCreator { context -> + SingleBindableStatusBarIconView.createView(context).also { view -> + view.initView(slot) { DeviceBasedSatelliteIconBinder.bind(view, viewModel) } + } + } + + override val shouldBindIcon: Boolean = oemEnabledSatelliteFlag() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt new file mode 100644 index 000000000000..59ac5f29b66c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.ui.binder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.common.ui.binder.IconViewBinder +import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView +import kotlinx.coroutines.launch + +object DeviceBasedSatelliteIconBinder { + fun bind( + view: SingleBindableStatusBarIconView, + viewModel: DeviceBasedSatelliteViewModel, + ): ModernStatusBarViewBinding { + return SingleBindableStatusBarIconView.withDefaultBinding( + view = view, + shouldBeVisible = { viewModel.icon.value != null } + ) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.icon.collect { newIcon -> + if (newIcon == null) { + view.iconView.setImageDrawable(null) + } else { + IconViewBinder.bind(newIcon, view.iconView) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt new file mode 100644 index 000000000000..6938d667ca81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.ui.model + +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.res.R +import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState + +/** + * Define the [Icon] that relates to a given satellite connection state + level. Note that for now + * We don't need any data class box, so we can just use a simple mapping function. + */ +object SatelliteIconModel { + fun fromConnectionState( + connectionState: SatelliteConnectionState, + signalStrength: Int, + ): Icon? = + when (connectionState) { + // TODO(b/316635648): check if this should be null + SatelliteConnectionState.Unknown, + SatelliteConnectionState.Off, + SatelliteConnectionState.On -> + Icon.Resource( + res = R.drawable.ic_satellite_not_connected, + contentDescription = null, + ) + SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength) + } + + private fun fromSignalStrength( + signalStrength: Int, + ): Icon? = + // TODO(b/316634365): these need content descriptions + when (signalStrength) { + // No signal + 0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null) + + // Poor -> Moderate + 1, + 2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null) + + // Good -> Great + 3, + 4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null) + else -> null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt new file mode 100644 index 000000000000..0051161eff35 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel + +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor +import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn + +/** + * View-Model for the device-based satellite icon. This icon will only show in the status bar if + * satellite is available AND all other service states are considered OOS. + */ +@OptIn(ExperimentalCoroutinesApi::class) +class DeviceBasedSatelliteViewModel +@Inject +constructor( + interactor: DeviceBasedSatelliteInteractor, + @Application scope: CoroutineScope, +) { + private val shouldShowIcon: StateFlow<Boolean> = + interactor.areAllConnectionsOutOfService + .flatMapLatest { allOos -> + if (!allOos) { + flowOf(false) + } else { + interactor.isSatelliteAllowed + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + val icon: StateFlow<Icon?> = + combine( + shouldShowIcon, + interactor.connectionState, + interactor.signalStrength, + ) { shouldShow, state, signalStrength -> + if (shouldShow) { + SatelliteIconModel.fromConnectionState(state, signalStrength) + } else { + null + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt index 3b87bed2e0ef..25a2c9dd3caf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -103,7 +103,7 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) : * * Creates a dot view, and uses [bindingCreator] to get and set the binding. */ - fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) { + open fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) { // The dot view requires [slot] to be set, and the [binding] may require an instantiated dot // view. So, this is the required order. this.slot = slot diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt new file mode 100644 index 000000000000..c663c37fec98 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.view + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +/** Simple single-icon view that is bound to bindable_status_bar_icon.xml */ +class SingleBindableStatusBarIconView( + context: Context, + attrs: AttributeSet?, +) : ModernStatusBarView(context, attrs) { + + internal lateinit var iconView: ImageView + internal lateinit var dotView: StatusBarIconView + + override fun toString(): String { + return "SingleBindableStatusBarIcon(" + + "slot='$slot', " + + "isCollecting=${binding.isCollecting()}, " + + "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " + + "viewString=${super.toString()}" + } + + override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) { + super.initView(slot, bindingCreator) + + iconView = requireViewById(R.id.icon_view) + dotView = requireViewById(R.id.status_bar_dot) + } + + companion object { + fun createView( + context: Context, + ): SingleBindableStatusBarIconView { + return LayoutInflater.from(context).inflate(R.layout.bindable_status_bar_icon, null) + as SingleBindableStatusBarIconView + } + + /** + * Using a given binding [block], create the necessary scaffolding to handle the general + * case of a single status bar icon. This includes eliding into a dot view when there is not + * enough space, and handling tint. + * + * [block] should be a simple [launch] call that handles updating the single icon view with + * its new view. Currently there is no simple way to e.g., extend to handle multiple tints + * for dual-layered icons, and any more complex logic should probably find a way to return + * its own version of [ModernStatusBarViewBinding]. + */ + fun withDefaultBinding( + view: SingleBindableStatusBarIconView, + shouldBeVisible: () -> Boolean, + block: suspend LifecycleOwner.(View) -> Unit + ): SingleBindableStatusBarIconViewBinding { + @StatusBarIconView.VisibleState + val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN) + + val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE) + val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE) + + var isCollecting: Boolean = false + + view.repeatWhenAttached { + // Child binding + block(view) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + // isVisible controls the visibility state of the outer group, and thus it + // needs + // to run in the CREATED lifecycle so it can continue to watch while + // invisible + // See (b/291031862) for details + launch { + visibilityState.collect { visibilityState -> + // for b/296864006, we can not hide all the child views if + // visibilityState is STATE_HIDDEN. Because hiding all child views + // would cause the + // getWidth() of this view return 0, and that would cause the + // translation + // calculation fails in StatusIconContainer. Therefore, like class + // MobileIconBinder, instead of set the child views visibility to + // View.GONE, + // we set their visibility to View.INVISIBLE to make them invisible + // but + // keep the width. + ModernStatusBarViewVisibilityHelper.setVisibilityState( + visibilityState, + view.iconView, + view.dotView, + ) + } + } + + launch { + iconTint.collect { tint -> + val tintList = ColorStateList.valueOf(tint) + view.iconView.imageTintList = tintList + view.dotView.setDecorColor(tint) + } + } + + launch { + decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) } + } + + try { + awaitCancellation() + } finally { + isCollecting = false + } + } + } + } + + return object : SingleBindableStatusBarIconViewBinding { + override val decorTint: Int + get() = decorTint.value + + override val iconTint: Int + get() = iconTint.value + + override fun getShouldIconBeVisible(): Boolean { + return shouldBeVisible() + } + + override fun onVisibilityStateChanged(state: Int) { + visibilityState.value = state + } + + override fun onIconTintChanged(newTint: Int, contrastTint: Int) { + iconTint.value = newTint + } + + override fun onDecorTintChanged(newTint: Int) { + decorTint.value = newTint + } + + override fun isCollecting(): Boolean { + return isCollecting + } + } + } + } +} + +@VisibleForTesting +interface SingleBindableStatusBarIconViewBinding : ModernStatusBarViewBinding { + val iconTint: Int + val decorTint: Int +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 1528c9befda7..1414150c5511 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -159,7 +159,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { public void showNotification(@NonNull NotificationEntry entry) { mLogger.logShowNotification(entry); addEntry(entry); - updateNotification(entry.getKey(), true /* show */); + updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */); entry.setInterruption(); } @@ -190,12 +190,12 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { /** * Called when the notification state has been updated. * @param key the key of the entry that was updated - * @param show whether the notification should show again and force reevaluation of - * removal time + * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation + * of removal time */ - public void updateNotification(@NonNull String key, boolean show) { + public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); - mLogger.logUpdateNotification(key, show, headsUpEntry != null); + mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null); if (headsUpEntry == null) { // the entry was released before this update (i.e by a listener) This can happen // with the groupmanager @@ -204,7 +204,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - if (show) { + if (shouldHeadsUpAgain) { headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification"); if (headsUpEntry != null) { setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt index b8c7e202ce7c..a7352be8d80a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt @@ -182,7 +182,7 @@ interface HeadsUpManager : Dumpable { */ fun unpinAll(userUnPinned: Boolean) - fun updateNotification(key: String, alert: Boolean) + fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) } /** Sets the animation state of the HeadsUpManager. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java new file mode 100644 index 000000000000..970cc75bbb6b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A controller which provides the current sensitive notification protections status as well as + * to assist in feature usage and exemptions + */ +public interface SensitiveNotificationProtectionController { + /** + * Register a runnable that triggers on changes to protection state + * + * <p> onSensitiveStateChanged not invoked on registration + */ + void registerSensitiveStateListener(Runnable onSensitiveStateChanged); + + /** Unregister a previously registered onSensitiveStateChanged runnable */ + void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged); + + /** Return {@code true} if device in state in which notifications should be protected */ + boolean isSensitiveStateActive(); + + /** Return {@code true} when notification should be protected */ + boolean shouldProtectNotification(NotificationEntry entry); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java new file mode 100644 index 000000000000..3c4ca4465874 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import static com.android.systemui.Flags.screenshareNotificationHiding; + +import android.media.projection.MediaProjectionInfo; +import android.media.projection.MediaProjectionManager; +import android.os.Handler; +import android.os.Trace; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.util.ListenerSet; + +import javax.inject.Inject; + +/** Implementation of SensitiveNotificationProtectionController. **/ +@SysUISingleton +public class SensitiveNotificationProtectionControllerImpl + implements SensitiveNotificationProtectionController { + private final MediaProjectionManager mMediaProjectionManager; + private final ListenerSet<Runnable> mListeners = new ListenerSet<>(); + private volatile MediaProjectionInfo mProjection; + + @VisibleForTesting + final MediaProjectionManager.Callback mMediaProjectionCallback = + new MediaProjectionManager.Callback() { + @Override + public void onStart(MediaProjectionInfo info) { + Trace.beginSection( + "SNPC.onProjectionStart"); + mProjection = info; + mListeners.forEach(Runnable::run); + Trace.endSection(); + } + + @Override + public void onStop(MediaProjectionInfo info) { + Trace.beginSection( + "SNPC.onProjectionStop"); + mProjection = null; + mListeners.forEach(Runnable::run); + Trace.endSection(); + } + }; + + @Inject + public SensitiveNotificationProtectionControllerImpl( + MediaProjectionManager mediaProjectionManager, + @Main Handler mainHandler) { + mMediaProjectionManager = mediaProjectionManager; + + if (screenshareNotificationHiding()) { + mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler); + } + } + + @Override + public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) { + mListeners.addIfAbsent(onSensitiveStateChanged); + } + + @Override + public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) { + mListeners.remove(onSensitiveStateChanged); + } + + @Override + public boolean isSensitiveStateActive() { + // TODO(b/316955558): Add disabled by developer option + // TODO(b/316955306): Add feature exemption for sysui and bug handlers + // TODO(b/316955346): Add feature exemption for single app screen sharing + return mProjection != null; + } + + @Override + public boolean shouldProtectNotification(NotificationEntry entry) { + if (!isSensitiveStateActive()) { + return false; + } + + // Exempt foreground service notifications from protection in effort to keep screen share + // stop actions easily accessible + // TODO(b/316955208): Exempt FGS notifications only for app that started projection + return !entry.getSbn().getNotification().isFgsOrUij(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 3304b9827fd8..15200bd0ac54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -60,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; @@ -146,6 +148,11 @@ public interface StatusBarPolicyModule { /** */ @Binds + SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController( + SensitiveNotificationProtectionControllerImpl controllerImpl); + + /** */ + @Binds UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt index ce811e205436..10137a0d3430 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt @@ -17,10 +17,16 @@ package com.android.systemui.statusbar.ui import com.android.internal.policy.SystemBarUtils +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -31,6 +37,8 @@ import kotlinx.coroutines.flow.onStart class SystemBarUtilsState @Inject constructor( + @Background bgContext: CoroutineContext, + @Main mainContext: CoroutineContext, configurationController: ConfigurationController, proxy: SystemBarUtilsProxy, ) { @@ -38,5 +46,10 @@ constructor( val statusBarHeight: Flow<Int> = configurationController.onConfigChanged .onStart<Any> { emit(Unit) } + .flowOn(mainContext) + .conflate() .map { proxy.getStatusBarHeight() } + .distinctUntilChanged() + .flowOn(bgContext) + .conflate() } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt index b8f95832b852..1ba269e25dff 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt @@ -24,7 +24,7 @@ import android.content.IntentFilter import android.os.UserHandle import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository @@ -54,7 +54,7 @@ interface WallpaperRepository { class WallpaperRepositoryImpl @Inject constructor( - @Application scope: CoroutineScope, + @Background scope: CoroutineScope, broadcastDispatcher: BroadcastDispatcher, userRepository: UserRepository, private val wallpaperManager: WallpaperManager, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index c2efc05132ba..c2efc05132ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 0959f1b2bcf6..0959f1b2bcf6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 1281e4409a83..1281e4409a83 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 5e5273b779c6..bc9a0a5484ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -181,16 +181,16 @@ public class MenuViewLayerTest extends SysuiTestCase { @Test public void onAttachedToWindow_menuIsVisible() { mMenuViewLayer.onAttachedToWindow(); - final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); + final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); assertThat(menuView.getVisibility()).isEqualTo(VISIBLE); } @Test - public void onAttachedToWindow_menuIsGone() { + public void onDetachedFromWindow_menuIsGone() { mMenuViewLayer.onDetachedFromWindow(); - final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); + final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); assertThat(menuView.getVisibility()).isEqualTo(GONE); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index b57cf53911e9..754a7fd81475 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -97,6 +97,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.scene.FakeWindowRootViewComponent; @@ -150,6 +151,7 @@ import kotlinx.coroutines.test.TestScope; @TestableLooper.RunWithLooper @SmallTest public class KeyguardViewMediatorTest extends SysuiTestCase { + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); private KeyguardViewMediator mViewMediator; private final TestScope mTestScope = TestScopeProvider.getTestScope(); @@ -265,7 +267,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mShadeWindowLogger, () -> mSelectedUserInteractor, mUserTracker, - mSceneContainerFlags); + mSceneContainerFlags, + mKosmos::getCommunalInteractor); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 90eaa5a2eeaf..dafd9e64371b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -23,8 +23,9 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.flags.FakeFeatureFlags @@ -34,6 +35,8 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel @@ -42,11 +45,15 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -57,7 +64,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent @@ -83,7 +89,8 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(JUnit4::class) class KeyguardTransitionScenariosTest : SysuiTestCase() { - private lateinit var testScope: TestScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var bouncerRepository: FakeKeyguardBouncerRepository @@ -119,17 +126,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - val testDispatcher = StandardTestDispatcher() - testScope = TestScope(testDispatcher) - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() + keyguardRepository = kosmos.fakeKeyguardRepository + bouncerRepository = kosmos.fakeKeyguardBouncerRepository commandQueue = FakeCommandQueue() - shadeRepository = FakeShadeRepository() - transitionRepository = spy(FakeKeyguardTransitionRepository()) + shadeRepository = kosmos.fakeShadeRepository + transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository) powerInteractor = PowerInteractorFactory.create().powerInteractor - communalInteractor = - CommunalInteractorFactory.create(testScope = testScope).communalInteractor + communalInteractor = kosmos.communalInteractor whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) @@ -160,8 +164,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -184,8 +188,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -199,8 +203,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -210,8 +214,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingLockscreenHostedTransitionInteractor = FromDreamingLockscreenHostedTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -221,8 +225,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAodTransitionInteractor = FromAodTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -232,8 +236,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromGoneTransitionInteractor = FromGoneTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -244,8 +248,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDozingTransitionInteractor = FromDozingTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -257,8 +261,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -269,8 +273,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, @@ -281,8 +285,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromGlanceableHubTransitionInteractor = FromGlanceableHubTransitionInteractor( scope = testScope, - bgDispatcher = testDispatcher, - mainDispatcher = testDispatcher, + bgDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, glanceableHubTransitions = glanceableHubTransitions, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 6be92756ba8a..ba7927d6b8bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -25,8 +25,7 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.data.repository.FakeCommunalRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController @@ -43,6 +42,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.testKosmos import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -79,6 +79,8 @@ import org.mockito.junit.MockitoJUnit @TestableLooper.RunWithLooper(setAsMainLooper = true) class MediaHierarchyManagerTest : SysuiTestCase() { + private val kosmos = testKosmos() + @Mock private lateinit var lockHost: MediaHost @Mock private lateinit var qsHost: MediaHost @Mock private lateinit var qqsHost: MediaHost @@ -110,10 +112,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var isQsBypassingShade: MutableStateFlow<Boolean> private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() - private val communalRepository = - FakeCommunalRepository(applicationScope = testScope.backgroundScope) - private val communalInteractor = - CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor + private val communalInteractor = kosmos.communalInteractor private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 0a464e6047d3..b701d7f315bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -21,6 +21,7 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.models.player.MediaViewHolder @@ -171,6 +172,38 @@ class MediaViewControllerTest : SysuiTestCase() { } @Test + fun testObtainViewState_expandedMatchesParentHeight() { + mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + player.measureState = + TransitionViewState().apply { + this.height = 100 + this.measureHeight = 100 + } + mediaHostStateHolder.expandedMatchesParentHeight = true + mediaHostStateHolder.expansion = 1f + mediaHostStateHolder.measurementInput = + MeasurementInput( + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), + ) + + // Assign the height of each expanded layout + MediaViewHolder.backgroundIds.forEach { id -> + mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 100 + } + + mediaViewController.obtainViewState(mediaHostStateHolder) + + // Verify height of each expanded layout is updated to match constraint + MediaViewHolder.backgroundIds.forEach { id -> + assertTrue( + mediaViewController.expandedLayout.getConstraint(id).layout.mHeight == + ConstraintSet.MATCH_CONSTRAINT + ) + } + } + + @Test fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() { whenever(mockViewState.copy()).thenReturn(mockCopiedState) whenever(mockCopiedState.widgetStates) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 72847a6b6c45..c6cfabc61300 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -414,6 +414,18 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_verifyDeviceListCallback() { + // This test relies on mMediaOutputController.start being called while the selected device + // list has exactly one item, and that item's id is: + // - Different from both ids in mMediaDevices. + // - Different from the id of the route published by the device under test (usually the + // built-in speakers). + // So mock the selected device to respect these two preconditions. + MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class); + when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID); + doReturn(List.of(mockSelectedMediaDevice)) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + mMediaOutputController.start(mCb); reset(mCb); @@ -434,6 +446,18 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() { + // This test relies on mMediaOutputController.start being called while the selected device + // list has exactly one item, and that item's id is: + // - Different from both ids in mMediaDevices. + // - Different from the id of the route published by the device under test (usually the + // built-in speakers). + // So mock the selected device to respect these two preconditions. + MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class); + when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID); + doReturn(List.of(mockSelectedMediaDevice)) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + when(mMediaDevice1.getFeatures()).thenReturn( ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index b7a9ea751438..81ff817830ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -25,14 +25,16 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.compose.ComposeFacade import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -52,6 +54,8 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class GlanceableHubContainerControllerTest : SysuiTestCase() { + private val kosmos = testKosmos() + @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var shadeInteractor: ShadeInteractor @@ -71,9 +75,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - val withDeps = CommunalInteractorFactory.create() - communalInteractor = withDeps.communalInteractor - communalRepository = withDeps.communalRepository + communalInteractor = kosmos.communalInteractor + communalRepository = kosmos.fakeCommunalRepository underTest = GlanceableHubContainerController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 11da2376328a..200e758245a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -57,7 +57,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -202,8 +201,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), shadeRepository, () -> sceneInteractor); - CommunalInteractor communalInteractor = - CommunalInteractorFactory.create().getCommunalInteractor(); + CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); FakeKeyguardTransitionRepository keyguardTransitionRepository = new FakeKeyguardTransitionRepository(); @@ -303,7 +301,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mShadeWindowLogger, () -> mSelectedUserInteractor, mUserTracker, - mSceneContainerFlags) { + mSceneContainerFlags, + () -> communalInteractor) { @Override protected boolean isDebuggable() { return false; @@ -451,6 +450,24 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test + public void setCommunalShowing_userTimeout() { + setKeyguardShowing(); + clearInvocations(mWindowManager); + + mNotificationShadeWindowController.onCommunalShowingChanged(true); + verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat(mLayoutParameters.getValue().userActivityTimeout) + .isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS); + clearInvocations(mWindowManager); + + // Bouncer showing over communal overrides communal value + mNotificationShadeWindowController.setBouncerShowing(true); + verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat(mLayoutParameters.getValue().userActivityTimeout) + .isEqualTo(KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS); + } + + @Test public void setKeyguardShowing_notFocusable_byDefault() { mNotificationShadeWindowController.setKeyguardShowing(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 8f46a37bf540..6fc88ce95325 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -43,7 +43,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; @@ -233,8 +232,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), mShadeRepository, () -> sceneInteractor); - CommunalInteractor communalInteractor = - CommunalInteractorFactory.create().getCommunalInteractor(); + CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); FakeKeyguardTransitionRepository keyguardTransitionRepository = new FakeKeyguardTransitionRepository(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 05e866e85112..13934dac2401 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -27,7 +27,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeCommandQueue @@ -144,7 +144,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { { fromLockscreenTransitionInteractor }, { fromPrimaryBouncerTransitionInteractor } ) - val communalInteractor = CommunalInteractorFactory.create().communalInteractor + val communalInteractor = kosmos.communalInteractor fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( keyguardTransitionRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt index df547ae5883e..350ed2d9ff22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -17,9 +17,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle +import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -33,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -55,28 +58,31 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { val statusBarStateController: StatusBarStateController = mock() val keyguardStateController: KeyguardStateController = mock() val mSelectedUserInteractor: SelectedUserInteractor = mock() + val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController = + mock() val coordinator: SensitiveContentCoordinator = - DaggerTestSensitiveContentCoordinatorComponent - .factory() - .create( - dynamicPrivacyController, - lockscreenUserManager, - keyguardUpdateMonitor, - statusBarStateController, - keyguardStateController, - mSelectedUserInteractor) - .coordinator + DaggerTestSensitiveContentCoordinatorComponent.factory() + .create( + dynamicPrivacyController, + lockscreenUserManager, + keyguardUpdateMonitor, + statusBarStateController, + keyguardStateController, + mSelectedUserInteractor, + sensitiveNotificationProtectionController + ) + .coordinator @Test fun onDynamicPrivacyChanged_invokeInvalidationListener() { coordinator.attach(pipeline) - val invalidator = withArgCaptor<Invalidator> { - verify(pipeline).addPreRenderInvalidator(capture()) - } - val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> { - verify(dynamicPrivacyController).addListener(capture()) - } + val invalidator = + withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } + val dynamicPrivacyListener = + withArgCaptor<DynamicPrivacyController.Listener> { + verify(dynamicPrivacyController).addListener(capture()) + } val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() invalidator.setInvalidationListener(invalidationListener) @@ -89,9 +95,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { @Test fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) @@ -105,11 +112,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, false) + } + + @Test fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) @@ -123,11 +178,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, false) + } + + @Test fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) @@ -141,17 +244,87 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, false) + } + + @Test fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -159,17 +332,92 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -179,15 +427,37 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { @Test fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -195,19 +465,96 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + @Suppress("ktlint:standard:max-line-length") + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) val entry = fakeNotification(2, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) + ) + .thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -217,9 +564,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { @Test fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() { coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } whenever(lockscreenUserManager.currentUserId).thenReturn(1) whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) @@ -227,9 +575,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD) whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any())) - .thenReturn(true) - + .thenReturn(true) val entry = fakeNotification(2, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) + whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any())) + .thenReturn(true) onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) @@ -237,15 +587,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { } private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { - val mockUserHandle = mock<UserHandle>().apply { - whenever(identifier).thenReturn(notifUserId) - } - val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply { - whenever(user).thenReturn(mockUserHandle) - } - val mockEntry = mock<NotificationEntry>().apply { - whenever(sbn).thenReturn(mockSbn) - } + val mockUserHandle = + mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) } + val mockSbn: StatusBarNotification = + mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) } + val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) } whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction) whenever(mockEntry.rowExists()).thenReturn(true) return object : ListEntry("key", 0) { @@ -268,6 +614,8 @@ interface TestSensitiveContentCoordinatorComponent { @BindsInstance statusBarStateController: StatusBarStateController, @BindsInstance keyguardStateController: KeyguardStateController, @BindsInstance selectedUserInteractor: SelectedUserInteractor, + @BindsInstance + sensitiveNotificationProtectionController: SensitiveNotificationProtectionController, ): TestSensitiveContentCoordinatorComponent } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 89f826b2049d..1ab4c32c7d08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING; import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -32,6 +33,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static kotlinx.coroutines.flow.FlowKt.emptyFlow; @@ -39,6 +41,7 @@ import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDis import android.metrics.LogMaker; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -101,6 +104,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.SecureSettings; @@ -172,10 +176,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private ActivityStarter mActivityStarter; @Mock private KeyguardTransitionRepository mKeyguardTransitionRepo; @Mock private NotificationListViewBinder mViewBinder; + @Mock + private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController; + + @Captor + private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; + private final ActiveNotificationListRepository mActiveNotificationsRepository = new ActiveNotificationListRepository(); @@ -386,6 +396,23 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void testOnUserChange_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); initController(/* viewIsAttached= */ true); @@ -403,6 +430,80 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + initController(/* viewIsAttached= */ true); + + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); + + verify(mNotificationLockscreenUserManager) + .addUserChangedListener(userChangedCaptor.capture()); + reset(mNotificationStackScrollLayout); + + UserChangedListener changedListener = userChangedCaptor.getValue(); + changedListener.onUserChanged(0); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + public void testOnStatePostChange_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test public void testOnStatePostChange_verifyIfProfileIsPublic() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); @@ -418,6 +519,194 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + public void testOnStatePostChange_goingFullShade_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, false); + } + + @Test + public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(true, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled( + ) { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSysuiStatusBarStateController).addCallback( + mStateListenerArgumentCaptor.capture(), anyInt()); + + StatusBarStateController.StateListener stateListener = + mStateListenerArgumentCaptor.getValue(); + + stateListener.onStatePostChange(); + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnProjectionStateChanged_verifyNotSensitive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()) + .thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController) + .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture()); + + mSensitiveStateListenerArgumentCaptor.getValue().run(); + + verify(mNotificationStackScrollLayout).updateSensitiveness(false, false); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnProjectionStateChanged_verifyIfProfileIsPublic() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false); + + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController) + .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture()); + + mSensitiveStateListenerArgumentCaptor.getValue().run(); + + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void testOnProjectionStateChanged_verifyIfSensitiveActive() { + when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); + when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true); + + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController) + .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture()); + + mSensitiveStateListenerArgumentCaptor.getValue().run(); + + verify(mNotificationStackScrollLayout).updateSensitiveness(false, true); + } + + @Test public void testOnMenuShownLogging() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker( @@ -666,6 +955,20 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean()); } + @Test + @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void sensitiveNotificationProtectionControllerListenerNotRegistered() { + initController(/* viewIsAttached= */ true); + verifyZeroInteractions(mSensitiveNotificationProtectionController); + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + public void sensitiveNotificationProtectionControllerListenerRegistered() { + initController(/* viewIsAttached= */ true); + verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any()); + } + private LogMaker logMatcher(int category, int type) { return argThat(new LogMatcher(category, type)); } @@ -744,7 +1047,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mSecureSettings, mock(NotificationDismissibilityProvider.class), mActivityStarter, - new ResourcesSplitShadeStateController()); + new ResourcesSplitShadeStateController(), + mSensitiveNotificationProtectionController); } static class LogMatcher implements ArgumentMatcher<LogMaker> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 4827c92ce452..d9eaea1367cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -66,6 +66,7 @@ import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -145,6 +146,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock private KeyguardInteractor mKeyguardInteractor; private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository(); @Mock private CoroutineDispatcher mMainDispatcher; @Mock private TypedArray mMockTypedArray; @@ -292,6 +294,7 @@ public class ScrimControllerTest extends SysuiTestCase { mPrimaryBouncerToGoneTransitionViewModel, mAlternateBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, + mKeyguardInteractor, mWallpaperRepository, mMainDispatcher, mLinearLargeScreenShadeInterpolator); @@ -1000,6 +1003,7 @@ public class ScrimControllerTest extends SysuiTestCase { mPrimaryBouncerToGoneTransitionViewModel, mAlternateBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, + mKeyguardInteractor, mWallpaperRepository, mMainDispatcher, mLinearLargeScreenShadeInterpolator); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 1f8cc54211e6..6785de930849 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -168,6 +168,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC assertThat(conn.carrierName.value) .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice) + assertThat(conn.isNonTerrestrial.value).isEqualTo(model.ntn) // TODO(b/261029387): check these once we start handling them assertThat(conn.isEmergencyOnly.value).isFalse() @@ -194,6 +195,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC val roaming: Boolean, val name: String, val slice: Boolean, + val ntn: Boolean, ) { override fun toString(): String { return "INPUT(level=$level, " + @@ -205,7 +207,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC "carrierNetworkChange=$carrierNetworkChange, " + "roaming=$roaming, " + "name=$name," + - "slice=$slice)" + "slice=$slice" + + "ntn=$ntn)" } // Convenience for iterating test data and creating new cases @@ -220,6 +223,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC roaming: Boolean? = null, name: String? = null, slice: Boolean? = null, + ntn: Boolean? = null, ): TestCase = TestCase( level = level ?: this.level, @@ -232,6 +236,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC roaming = roaming ?: this.roaming, name = name ?: this.name, slice = slice ?: this.slice, + ntn = ntn ?: this.ntn, ) } @@ -262,6 +267,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC private val roaming = listOf(false, true) private val names = listOf("name 1", "name 2") private val slice = listOf(false, true) + private val ntn = listOf(false, true) @Parameters(name = "{0}") @JvmStatic fun data() = testData() @@ -300,6 +306,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC roaming.first(), names.first(), slice.first(), + ntn.first(), ) val tail = @@ -312,7 +319,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) }, roaming.map { baseCase.modifiedBy(roaming = it) }, names.map { baseCase.modifiedBy(name = it) }, - slice.map { baseCase.modifiedBy(slice = it) } + slice.map { baseCase.modifiedBy(slice = it) }, + ntn.map { baseCase.modifiedBy(ntn = it) } ) .flatten() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index 03814bdfa430..b958f35c0e53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -579,6 +579,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(conn.carrierName.value) .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice) + assertThat(conn.isNonTerrestrial.value).isEqualTo(model.ntn) // TODO(b/261029387) check these once we start handling them assertThat(conn.isEmergencyOnly.value).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 9d6f3156f83e..98556514f8ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback +import android.platform.test.annotations.EnableFlags import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL @@ -963,6 +964,31 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + fun isNonTerrestrial_updatesFromServiceState() = + testScope.runTest { + val latest by collectLastValue(underTest.isNonTerrestrial) + + // Lambda makes it a little clearer what we are testing IMO + val serviceStateCreator = { ntn: Boolean -> + mock<ServiceState>().also { + whenever(it.isUsingNonTerrestrialNetwork).thenReturn(ntn) + } + } + + // Starts out false + assertThat(latest).isFalse() + + getTelephonyCallbackForType<ServiceStateListener>() + .onServiceStateChanged(serviceStateCreator(true)) + assertThat(latest).isTrue() + + getTelephonyCallbackForType<ServiceStateListener>() + .onServiceStateChanged(serviceStateCreator(false)) + assertThat(latest).isFalse() + } + + @Test fun numberOfLevels_usesCarrierConfig() = testScope.runTest { var latest: Int? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 02e6fd5a9d6e..0e0d4897d667 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -35,6 +35,7 @@ import android.telephony.satellite.SatelliteModemStateCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState @@ -87,6 +88,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { Optional.empty(), dispatcher, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), systemClock, ) @@ -100,6 +102,45 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { } @Test + fun satelliteManagerThrows_checkSupportDoesNotCrash() = + testScope.runTest { + whenever(satelliteManager.requestIsSatelliteSupported(any(), any())) + .thenThrow(IllegalStateException()) + + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME) + + underTest = + DeviceBasedSatelliteRepositoryImpl( + Optional.of(satelliteManager), + dispatcher, + testScope.backgroundScope, + FakeLogBuffer.Factory.create(), + systemClock, + ) + + runCurrent() + + // Creating the repo does not crash, and we consider the feature not to be supported + assertThat(underTest.satelliteSupport.value).isEqualTo(SatelliteSupport.NotSupported) + } + + @Test + fun satelliteManagerThrows_doesNotCrash() = + testScope.runTest { + setupDefaultRepo() + + whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any())) + .thenThrow(SatelliteException(13)) + + val conn by collectLastValue(underTest.connectionState) + val strength by collectLastValue(underTest.signalStrength) + + // Flows have not emitted, we haven't crashed + assertThat(conn).isNull() + assertThat(strength).isNull() + } + + @Test fun connectionState_mapsFromSatelliteModemState() = testScope.runTest { setupDefaultRepo() @@ -380,6 +421,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { if (satMan != null) Optional.of(satMan) else Optional.empty(), dispatcher, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), systemClock, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt new file mode 100644 index 000000000000..21c038ad476d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.mockito.MockitoAnnotations + +@SmallTest +class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { + private lateinit var underTest: DeviceBasedSatelliteViewModel + private lateinit var interactor: DeviceBasedSatelliteInteractor + + private val repo = FakeDeviceBasedSatelliteRepository() + private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + + private val testScope = TestScope() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + interactor = + DeviceBasedSatelliteInteractor( + repo, + mobileIconsInteractor, + testScope.backgroundScope, + ) + + underTest = + DeviceBasedSatelliteViewModel( + interactor, + testScope.backgroundScope, + ) + } + + @Test + fun icon_nullWhenShouldNotShow_satelliteNotAllowed() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + // GIVEN satellite is not allowed + repo.isSatelliteAllowedForCurrentLocation.value = false + + // GIVEN all icons are OOS + val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) + i1.isInService.value = false + + // THEN icon is null because we should not be showing it + assertThat(latest).isNull() + } + + @Test + fun icon_nullWhenShouldNotShow_notAllOos() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + // GIVEN satellite is allowed + repo.isSatelliteAllowedForCurrentLocation.value = true + + // GIVEN all icons are not OOS + val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) + i1.isInService.value = true + + // THEN icon is null because we have service + assertThat(latest).isNull() + } + + @Test + fun icon_satelliteIsOff() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + // GIVEN satellite is allowed + repo.isSatelliteAllowedForCurrentLocation.value = true + + // GIVEN all icons are OOS + val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) + i1.isInService.value = false + + // THEN icon is null because we have service + assertThat(latest).isInstanceOf(Icon::class.java) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt new file mode 100644 index 000000000000..ca9df57e8798 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.view + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.StatusBarIconView +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Being a simple subclass of [ModernStatusBarView], use the same basic test cases to verify the + * root behavior, and add testing for the new [SingleBindableStatusBarIconView.withDefaultBinding] + * method. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class SingleBindableStatusBarIconViewTest : SysuiTestCase() { + private lateinit var binding: SingleBindableStatusBarIconViewBinding + + // Visibility is outsourced to view-models. This simulates it + private var isVisible = true + private var visibilityFn: () -> Boolean = { isVisible } + + @Test + fun initView_hasCorrectSlot() { + val view = createAndInitView() + + assertThat(view.slot).isEqualTo(SLOT_NAME) + } + + @Test + fun getVisibleState_icon_returnsIcon() { + val view = createAndInitView() + + view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false) + + assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_ICON) + } + + @Test + fun getVisibleState_dot_returnsDot() { + val view = createAndInitView() + + view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false) + + assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_DOT) + } + + @Test + fun getVisibleState_hidden_returnsHidden() { + val view = createAndInitView() + + view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false) + + assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_HIDDEN) + } + + @Test + fun onDarkChanged_bindingReceivesIconAndDecorTint() { + val view = createAndInitView() + + view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321) + + assertThat(binding.iconTint).isEqualTo(0x12345678) + assertThat(binding.decorTint).isEqualTo(0x12345678) + } + + @Test + fun setStaticDrawableColor_bindingReceivesIconTint() { + val view = createAndInitView() + + view.setStaticDrawableColor(0x12345678, 0x12344321) + + assertThat(binding.iconTint).isEqualTo(0x12345678) + } + + @Test + fun setDecorColor_bindingReceivesDecorColor() { + val view = createAndInitView() + + view.setDecorColor(0x23456789) + + assertThat(binding.decorTint).isEqualTo(0x23456789) + } + + @Test + fun isIconVisible_usesBinding_true() { + val view = createAndInitView() + + isVisible = true + + assertThat(view.isIconVisible).isEqualTo(true) + } + + @Test + fun isIconVisible_usesBinding_false() { + val view = createAndInitView() + + isVisible = false + + assertThat(view.isIconVisible).isEqualTo(false) + } + + @Test + fun getDrawingRect_takesTranslationIntoAccount() { + val view = createAndInitView() + + view.translationX = 50f + view.translationY = 60f + + val drawingRect = Rect() + view.getDrawingRect(drawingRect) + + assertThat(drawingRect.left).isEqualTo(view.left + 50) + assertThat(drawingRect.right).isEqualTo(view.right + 50) + assertThat(drawingRect.top).isEqualTo(view.top + 60) + assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60) + } + + private fun createAndInitView(): SingleBindableStatusBarIconView { + val view = SingleBindableStatusBarIconView.createView(context) + binding = SingleBindableStatusBarIconView.withDefaultBinding(view, visibilityFn) {} + view.initView(SLOT_NAME) { binding } + return view + } + + companion object { + private const val SLOT_NAME = "test_slot" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt new file mode 100644 index 000000000000..cd5d5ed0d08e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.app.Notification +import android.media.projection.MediaProjectionInfo +import android.media.projection.MediaProjectionManager +import android.os.Handler +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { + @Mock private lateinit var handler: Handler + + @Mock private lateinit var mediaProjectionManager: MediaProjectionManager + + @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo + + @Mock private lateinit var listener1: Runnable + @Mock private lateinit var listener2: Runnable + @Mock private lateinit var listener3: Runnable + + @Captor + private lateinit var mediaProjectionCallbackCaptor: + ArgumentCaptor<MediaProjectionManager.Callback> + + private lateinit var controller: SensitiveNotificationProtectionControllerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) + + controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler) + + // Obtain useful MediaProjectionCallback + verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any()) + } + + @Test + fun init_flagEnabled_registerMediaProjectionManagerCallback() { + assertNotNull(mediaProjectionCallbackCaptor.value) + } + + @Test + fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() { + mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) + reset(mediaProjectionManager) + + controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler) + + verifyZeroInteractions(mediaProjectionManager) + } + + @Test + fun registerSensitiveStateListener_singleListener() { + controller.registerSensitiveStateListener(listener1) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + } + + @Test + fun registerSensitiveStateListener_multipleListeners() { + controller.registerSensitiveStateListener(listener1) + controller.registerSensitiveStateListener(listener2) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + verify(listener2, times(2)).run() + } + + @Test + fun registerSensitiveStateListener_afterProjectionActive() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + controller.registerSensitiveStateListener(listener1) + verifyZeroInteractions(listener1) + + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1).run() + } + + @Test + fun unregisterSensitiveStateListener_singleListener() { + controller.registerSensitiveStateListener(listener1) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + + controller.unregisterSensitiveStateListener(listener1) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verifyNoMoreInteractions(listener1) + } + + @Test + fun unregisterSensitiveStateListener_multipleListeners() { + controller.registerSensitiveStateListener(listener1) + controller.registerSensitiveStateListener(listener2) + controller.registerSensitiveStateListener(listener3) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verify(listener1, times(2)).run() + verify(listener2, times(2)).run() + verify(listener3, times(2)).run() + + controller.unregisterSensitiveStateListener(listener1) + controller.unregisterSensitiveStateListener(listener2) + + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + verifyNoMoreInteractions(listener1) + verifyNoMoreInteractions(listener2) + verify(listener3, times(4)).run() + } + + @Test + fun isSensitiveStateActive_projectionInactive_false() { + assertFalse(controller.isSensitiveStateActive) + } + + @Test + fun isSensitiveStateActive_projectionActive_true() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + assertTrue(controller.isSensitiveStateActive) + } + + @Test + fun isSensitiveStateActive_projectionInactiveAfterActive_false() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + + assertFalse(controller.isSensitiveStateActive) + } + + @Test + fun isSensitiveStateActive_projectionActiveAfterInactive_true() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo) + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + assertTrue(controller.isSensitiveStateActive) + } + + @Test + fun shouldProtectNotification_projectionInactive_false() { + val notificationEntry = mock(NotificationEntry::class.java) + + assertFalse(controller.shouldProtectNotification(notificationEntry)) + } + + @Test + fun shouldProtectNotification_projectionActive_fgsNotification_false() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + val notificationEntry = mock(NotificationEntry::class.java) + val sbn = mock(StatusBarNotification::class.java) + val notification = mock(Notification::class.java) + `when`(notificationEntry.sbn).thenReturn(sbn) + `when`(sbn.notification).thenReturn(notification) + `when`(notification.isFgsOrUij).thenReturn(true) + + assertFalse(controller.shouldProtectNotification(notificationEntry)) + } + + @Test + fun shouldProtectNotification_projectionActive_notFgsNotification_true() { + mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) + + val notificationEntry = mock(NotificationEntry::class.java) + val sbn = mock(StatusBarNotification::class.java) + val notification = mock(Notification::class.java) + `when`(notificationEntry.sbn).thenReturn(sbn) + `when`(sbn.notification).thenReturn(notification) + `when`(notification.isFgsOrUij).thenReturn(false) + + assertTrue(controller.shouldProtectNotification(notificationEntry)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 589f7c23ea13..93d8dcc8f092 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -100,7 +100,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -443,8 +442,7 @@ public class BubblesTest extends SysuiTestCase { () -> keyguardInteractor, () -> mFromLockscreenTransitionInteractor, () -> mFromPrimaryBouncerTransitionInteractor); - CommunalInteractor communalInteractor = - CommunalInteractorFactory.create().getCommunalInteractor(); + CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor( keyguardTransitionRepository, @@ -536,7 +534,8 @@ public class BubblesTest extends SysuiTestCase { mShadeWindowLogger, () -> mSelectedUserInteractor, mUserTracker, - mSceneContainerFlags + mSceneContainerFlags, + mKosmos::getCommunalInteractor ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index b820ca612bfc..1afe56f27be7 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.os.Looper; import android.os.SystemClock; import android.util.AndroidRuntimeException; +import android.util.Singleton; import android.view.Choreographer; import com.android.internal.util.Preconditions; @@ -67,7 +68,12 @@ import java.util.function.Consumer; public final class AnimatorTestRule implements TestRule { private final Object mLock = new Object(); - private final TestHandler mTestHandler = new TestHandler(); + private final Singleton<TestHandler> mTestHandler = new Singleton<>() { + @Override + protected TestHandler create() { + return new TestHandler(); + } + }; private final long mStartTime; private long mTotalTimeDelta = 0; @@ -95,16 +101,17 @@ public final class AnimatorTestRule implements TestRule { return new Statement() { @Override public void evaluate() throws Throwable { - AnimationHandler objAtStart = AnimationHandler.setTestHandler(mTestHandler); + final TestHandler testHandler = mTestHandler.get(); + AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler); try { base.evaluate(); } finally { AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart); - if (mTestHandler != objAtEnd) { + if (testHandler != objAtEnd) { // pass or fail, inner logic not restoring the handler needs to be reported. // noinspection ThrowFromFinallyBlock throw new IllegalStateException("Test handler was altered: expected=" - + mTestHandler + " actual=" + objAtEnd); + + testHandler + " actual=" + objAtEnd); } } } @@ -125,8 +132,9 @@ public final class AnimatorTestRule implements TestRule { public void initNewAnimators() { requireLooper("AnimationTestRule#initNewAnimators()"); long currentTime = getCurrentTime(); - List<AnimationFrameCallback> newCallbacks = new ArrayList<>(mTestHandler.mNewCallbacks); - mTestHandler.mNewCallbacks.clear(); + final TestHandler testHandler = mTestHandler.get(); + List<AnimationFrameCallback> newCallbacks = new ArrayList<>(testHandler.mNewCallbacks); + testHandler.mNewCallbacks.clear(); for (AnimationFrameCallback newCallback : newCallbacks) { newCallback.doAnimationFrame(currentTime); } @@ -158,9 +166,10 @@ public final class AnimatorTestRule implements TestRule { public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) { Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative"); requireLooper("AnimationTestRule#advanceTimeBy(long)"); + final TestHandler testHandler = mTestHandler.get(); if (timeDelta == 0) { // If time is not being advanced, all animators will get a tick; don't double tick these - mTestHandler.mNewCallbacks.clear(); + testHandler.mNewCallbacks.clear(); } else { // before advancing time, start new animators with the current time initNewAnimators(); @@ -172,10 +181,10 @@ public final class AnimatorTestRule implements TestRule { if (preFrameAction != null) { preFrameAction.accept(timeDelta); // After letting other code run, clear any new callbacks to avoid double-ticking them - mTestHandler.mNewCallbacks.clear(); + testHandler.mNewCallbacks.clear(); } // produce a frame - mTestHandler.doFrame(); + testHandler.doFrame(); } /** diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt deleted file mode 100644 index 1ba863052e2d..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.communal.domain.interactor - -import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository -import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository -import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.widgets.CommunalAppWidgetHost -import com.android.systemui.communal.widgets.EditWidgetsActivityStarter -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository -import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.test.TestScope - -// TODO(b/319335645): get rid of me and use kosmos. -object CommunalInteractorFactory { - - @JvmOverloads - @JvmStatic - fun create( - testScope: TestScope = TestScope(), - communalRepository: FakeCommunalRepository = - FakeCommunalRepository(testScope.backgroundScope), - keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(), - widgetRepository: FakeCommunalWidgetRepository = - FakeCommunalWidgetRepository(testScope.backgroundScope), - mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(), - smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(), - tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(), - communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(), - appWidgetHost: CommunalAppWidgetHost = mock(), - editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(), - ): WithDependencies { - val keyguardInteractor = - KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor - val communalTutorialInteractor = - CommunalTutorialInteractor( - testScope.backgroundScope, - tutorialRepository, - keyguardInteractor, - communalRepository, - ) - - return WithDependencies( - testScope, - communalRepository, - widgetRepository, - communalPrefsRepository, - mediaRepository, - smartspaceRepository, - tutorialRepository, - keyguardRepository, - keyguardInteractor, - communalTutorialInteractor, - appWidgetHost, - editWidgetsActivityStarter, - CommunalInteractor( - testScope.backgroundScope, - communalRepository, - widgetRepository, - communalPrefsRepository, - mediaRepository, - smartspaceRepository, - keyguardInteractor, - appWidgetHost, - editWidgetsActivityStarter, - ), - ) - } - - data class WithDependencies( - val testScope: TestScope, - val communalRepository: FakeCommunalRepository, - val widgetRepository: FakeCommunalWidgetRepository, - val communalPrefsRepository: FakeCommunalPrefsRepository, - val mediaRepository: FakeCommunalMediaRepository, - val smartspaceRepository: FakeSmartspaceRepository, - val tutorialRepository: FakeCommunalTutorialRepository, - val keyguardRepository: FakeKeyguardRepository, - val keyguardInteractor: KeyguardInteractor, - val tutorialInteractor: CommunalTutorialInteractor, - val appWidgetHost: CommunalAppWidgetHost, - val editWidgetsActivityStarter: EditWidgetsActivityStarter, - val communalInteractor: CommunalInteractor, - ) -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 65579a6d9ddf..1abf71fde14c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -20,11 +20,13 @@ import com.android.systemui.communal.data.repository.communalMediaRepository import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository +import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.smartspace.data.repository.smartspaceRepository +import com.android.systemui.user.data.repository.userRepository import com.android.systemui.util.mockito.mock val Kosmos.communalInteractor by Fixture { @@ -35,8 +37,11 @@ val Kosmos.communalInteractor by Fixture { mediaRepository = communalMediaRepository, communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, + userRepository = userRepository, appWidgetHost = mock(), keyguardInteractor = keyguardInteractor, - editWidgetsActivityStarter = mock(), + editWidgetsActivityStarter = editWidgetsActivityStarter, ) } + +val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt index baba88db8e55..adaea7cbf64d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt @@ -29,5 +29,6 @@ val Kosmos.communalTutorialInteractor by communalTutorialRepository = communalTutorialRepository, keyguardInteractor = keyguardInteractor, communalRepository = communalRepository, + communalInteractor = communalInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 59f56dd18f0e..5766f7a9028c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -130,6 +130,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _isEncryptedOrLockdown = MutableStateFlow(true) override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown + override val topClippingBounds = MutableStateFlow<Int?>(null) + override fun setQuickSettingsVisible(isVisible: Boolean) { _isQuickSettingsVisible.value = isVisible } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt deleted file mode 100644 index 92193fd6bc06..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2024 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.keyguard.domain.interactor - -import com.android.systemui.communal.data.repository.communalMediaRepository -import com.android.systemui.communal.data.repository.communalPrefsRepository -import com.android.systemui.communal.data.repository.communalRepository -import com.android.systemui.communal.data.repository.communalWidgetRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.widgets.CommunalAppWidgetHost -import com.android.systemui.communal.widgets.EditWidgetsActivityStarter -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.smartspace.data.repository.smartspaceRepository -import org.mockito.Mockito.mock - -val Kosmos.communalInteractor by - Kosmos.Fixture { - CommunalInteractor( - applicationScope = applicationCoroutineScope, - communalRepository = communalRepository, - widgetRepository = communalWidgetRepository, - mediaRepository = communalMediaRepository, - communalPrefsRepository = communalPrefsRepository, - smartspaceRepository = smartspaceRepository, - keyguardInteractor = keyguardInteractor, - appWidgetHost = mock(CommunalAppWidgetHost::class.java), - editWidgetsActivityStarter = editWidgetsActivityStarter, - ) - } - -val Kosmos.editWidgetsActivityStarter by - Kosmos.Fixture<EditWidgetsActivityStarter> { mock(EditWidgetsActivityStarter::class.java) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt index 294b5ba474c3..ec17c488e297 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index cc0449d7e7bb..321f94468a2e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -26,6 +26,7 @@ import com.android.systemui.classifier.falsingCollector import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -72,6 +73,7 @@ class KosmosJavaAdapter( val powerInteractor by lazy { kosmos.powerInteractor } val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } + val communalInteractor by lazy { kosmos.communalInteractor } init { kosmos.applicationContext = testCase.context diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index a9c8ec7dcb7d..32d572ef9dee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -35,6 +35,7 @@ class FakeMobileConnectionRepository( override val isRoaming = MutableStateFlow(false) override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null) override val isInService = MutableStateFlow(false) + override val isNonTerrestrial = MutableStateFlow(false) override val isGsm = MutableStateFlow(false) override val cdmaLevel = MutableStateFlow(0) override val primaryLevel = MutableStateFlow(0) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt index e208add32efa..5476d5509c0c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt @@ -17,7 +17,15 @@ package com.android.systemui.statusbar.ui import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.policy.configurationController -val Kosmos.systemBarUtilsState by - Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) } +val Kosmos.systemBarUtilsState by Fixture { + SystemBarUtilsState( + testDispatcher, + testDispatcher, + configurationController, + systemBarUtilsProxy, + ) +} diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index b5130a1c4cfc..ced10fbeff0c 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -34,3 +34,10 @@ flag { description: "Mitigation for autofill providers miscalculating view visibility" bug: "291795358" } + +flag { + name: "remote_fill_service_use_weak_reference" + namespace: "autofill" + description: "Use weak reference to address binder leak problem" + bug: "307972253" +} diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 6d0915bf7d92..5c93991bef8c 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -17,6 +17,7 @@ package com.android.server.autofill; import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; +import static android.service.autofill.Flags.remoteFillServiceUseWeakReference; import static com.android.server.autofill.Helper.sVerbose; @@ -44,6 +45,7 @@ import com.android.internal.infra.AbstractRemoteService; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.IResultReceiver; +import java.lang.ref.WeakReference; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -65,6 +67,8 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { private final Object mLock = new Object(); private CompletableFuture<FillResponse> mPendingFillRequest; private int mPendingFillRequestId = INVALID_REQUEST_ID; + private AtomicReference<IFillCallback> mFillCallback; + private AtomicReference<ISaveCallback> mSaveCallback; private final ComponentName mComponentName; private final boolean mIsCredentialAutofillService; @@ -150,6 +154,89 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } } + static class IFillCallbackDelegate extends IFillCallback.Stub { + private WeakReference<IFillCallback> mCallbackWeakRef; + + IFillCallbackDelegate(IFillCallback callback) { + mCallbackWeakRef = new WeakReference(callback); + } + + @Override + public void onCancellable(ICancellationSignal cancellation) throws RemoteException { + IFillCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onCancellable(cancellation); + } + } + + @Override + public void onSuccess(FillResponse response) throws RemoteException { + IFillCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onSuccess(response); + } + } + + @Override + public void onFailure(int requestId, CharSequence message) throws RemoteException { + IFillCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onFailure(requestId, message); + } + } + } + + static class ISaveCallbackDelegate extends ISaveCallback.Stub { + + private WeakReference<ISaveCallback> mCallbackWeakRef; + + ISaveCallbackDelegate(ISaveCallback callback) { + mCallbackWeakRef = new WeakReference(callback); + } + + @Override + public void onSuccess(IntentSender intentSender) throws RemoteException { + ISaveCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onSuccess(intentSender); + } + } + + @Override + public void onFailure(CharSequence message) throws RemoteException { + ISaveCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onFailure(message); + } + } + } + + /** + * Wraps an {@link IFillCallback} object using weak reference. + * + * This prevents lingering allocation issue by breaking the chain of strong references from + * Binder to {@link callback}. Since {@link callback} is not held by Binder anymore, it needs + * to be held by {@link mFillCallback} so it's not deallocated prematurely. + */ + private IFillCallback maybeWrapWithWeakReference(IFillCallback callback) { + if (remoteFillServiceUseWeakReference()) { + mFillCallback = new AtomicReference<>(callback); + return new IFillCallbackDelegate(callback); + } + return callback; + } + + /** + * Wraps an {@link ISaveCallback} object using weak reference. + */ + private ISaveCallback maybeWrapWithWeakReference(ISaveCallback callback) { + if (remoteFillServiceUseWeakReference()) { + mSaveCallback = new AtomicReference<>(callback); + return new ISaveCallbackDelegate(callback); + } + return callback; + } + public void onFillCredentialRequest(@NonNull FillRequest request, IAutoFillManagerClient autofillCallback) { if (sVerbose) { @@ -164,29 +251,30 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>(); - remoteService.onFillCredentialRequest(request, new IFillCallback.Stub() { - @Override - public void onCancellable(ICancellationSignal cancellation) { - CompletableFuture<FillResponse> future = futureRef.get(); - if (future != null && future.isCancelled()) { - dispatchCancellationSignal(cancellation); - } else { - cancellationSink.set(cancellation); - } - } - - @Override - public void onSuccess(FillResponse response) { - fillRequest.complete(response); - } - - @Override - public void onFailure(int requestId, CharSequence message) { - String errorMessage = message == null ? "" : String.valueOf(message); - fillRequest.completeExceptionally( - new RuntimeException(errorMessage)); - } - }, autofillCallback); + remoteService.onFillCredentialRequest( + request, maybeWrapWithWeakReference(new IFillCallback.Stub() { + @Override + public void onCancellable(ICancellationSignal cancellation) { + CompletableFuture<FillResponse> future = futureRef.get(); + if (future != null && future.isCancelled()) { + dispatchCancellationSignal(cancellation); + } else { + cancellationSink.set(cancellation); + } + } + + @Override + public void onSuccess(FillResponse response) { + fillRequest.complete(response); + } + + @Override + public void onFailure(int requestId, CharSequence message) { + String errorMessage = message == null ? "" : String.valueOf(message); + fillRequest.completeExceptionally( + new RuntimeException(errorMessage)); + } + }), autofillCallback); return fillRequest; }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); futureRef.set(connectThenFillRequest); @@ -235,29 +323,30 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>(); - remoteService.onFillRequest(request, new IFillCallback.Stub() { - @Override - public void onCancellable(ICancellationSignal cancellation) { - CompletableFuture<FillResponse> future = futureRef.get(); - if (future != null && future.isCancelled()) { - dispatchCancellationSignal(cancellation); - } else { - cancellationSink.set(cancellation); - } - } - - @Override - public void onSuccess(FillResponse response) { - fillRequest.complete(response); - } - - @Override - public void onFailure(int requestId, CharSequence message) { - String errorMessage = message == null ? "" : String.valueOf(message); - fillRequest.completeExceptionally( - new RuntimeException(errorMessage)); - } - }); + remoteService.onFillRequest( + request, maybeWrapWithWeakReference(new IFillCallback.Stub() { + @Override + public void onCancellable(ICancellationSignal cancellation) { + CompletableFuture<FillResponse> future = futureRef.get(); + if (future != null && future.isCancelled()) { + dispatchCancellationSignal(cancellation); + } else { + cancellationSink.set(cancellation); + } + } + + @Override + public void onSuccess(FillResponse response) { + fillRequest.complete(response); + } + + @Override + public void onFailure(int requestId, CharSequence message) { + String errorMessage = message == null ? "" : String.valueOf(message); + fillRequest.completeExceptionally( + new RuntimeException(errorMessage)); + } + })); return fillRequest; }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); futureRef.set(connectThenFillRequest); @@ -294,7 +383,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { if (sVerbose) Slog.v(TAG, "calling onSaveRequest()"); CompletableFuture<IntentSender> save = new CompletableFuture<>(); - service.onSaveRequest(request, new ISaveCallback.Stub() { + service.onSaveRequest(request, maybeWrapWithWeakReference(new ISaveCallback.Stub() { @Override public void onSuccess(IntentSender intentSender) { save.complete(intentSender); @@ -304,7 +393,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { public void onFailure(CharSequence message) { save.completeExceptionally(new RuntimeException(String.valueOf(message))); } - }); + })); return save; }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS) .whenComplete((res, err) -> Handler.getMain().post(() -> { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 50e18628852d..84e1d9062fd5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -247,6 +247,11 @@ public class CompanionDeviceManagerService extends SystemService { final Context context = getContext(); mPersistentStore = new PersistentDataStore(); + mAssociationRequestsProcessor = new AssociationRequestsProcessor( + /* cdmService */ this, mAssociationStore); + mBackupRestoreProcessor = new BackupRestoreProcessor( + /* cdmService */ this, mAssociationStore, mPersistentStore, + mSystemDataTransferRequestStore, mAssociationRequestsProcessor); loadAssociationsFromDisk(); mAssociationStore.registerListener(mAssociationStoreChangeListener); @@ -254,17 +259,12 @@ public class CompanionDeviceManagerService extends SystemService { mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager, mAssociationStore, mDevicePresenceCallback); - mAssociationRequestsProcessor = new AssociationRequestsProcessor( - /* cdmService */this, mAssociationStore); mCompanionAppController = new CompanionApplicationController( context, mAssociationStore, mDevicePresenceMonitor); mTransportManager = new CompanionTransportManager(context, mAssociationStore); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mPackageManagerInternal, mAssociationStore, mSystemDataTransferRequestStore, mTransportManager); - mBackupRestoreProcessor = new BackupRestoreProcessor( - /* cdmService */ this, mAssociationStore, mPersistentStore, - mSystemDataTransferRequestStore, mAssociationRequestsProcessor); // TODO(b/279663946): move context sync to a dedicated system service mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager); diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 3483c1a1404a..a493d7a57500 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -95,6 +95,7 @@ public class SystemConfig { private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100; private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200; private static final int ALLOW_VENDOR_APEX = 0x400; + private static final int ALLOW_SIGNATURE_PERMISSIONS = 0x800; private static final int ALLOW_ALL = ~0; // property for runtime configuration differentiation @@ -597,7 +598,7 @@ public class SystemConfig { // Vendors are only allowed to customize these int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS - | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX; + | ALLOW_SIGNATURE_PERMISSIONS | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX; if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) { // For backward compatibility vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS); @@ -649,9 +650,9 @@ public class SystemConfig { // TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited // the use of hidden APIs from the product partition. int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS - | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING - | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS - | ALLOW_VENDOR_APEX; + | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_SIGNATURE_PERMISSIONS + | ALLOW_HIDDENAPI_WHITELISTING | ALLOW_ASSOCIATIONS + | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS | ALLOW_VENDOR_APEX; if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) { // TODO(b/157393157): This must check product interface enforcement instead of // DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement. @@ -772,6 +773,8 @@ public class SystemConfig { final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0; + final boolean allowSignaturePermissions = (permissionFlag & ALLOW_SIGNATURE_PERMISSIONS) + != 0; final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0; final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0; @@ -1246,6 +1249,38 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); } } break; + case "signature-permissions": { + if (allowSignaturePermissions) { + // signature permissions from system, apex, vendor, product and + // system_ext partitions are stored separately. This is to + // prevent xml files in the vendor partition from granting + // permissions to signature apps in the system partition and vice versa. + boolean vendor = permFile.toPath().startsWith( + Environment.getVendorDirectory().toPath() + "/") + || permFile.toPath().startsWith( + Environment.getOdmDirectory().toPath() + "/"); + boolean product = permFile.toPath().startsWith( + Environment.getProductDirectory().toPath() + "/"); + boolean systemExt = permFile.toPath().startsWith( + Environment.getSystemExtDirectory().toPath() + "/"); + if (vendor) { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getVendorSignatureAppAllowlist()); + } else if (product) { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getProductSignatureAppAllowlist()); + } else if (systemExt) { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getSystemExtSignatureAppAllowlist()); + } else { + readSignatureAppPermissions(parser, + mPermissionAllowlist.getSignatureAppAllowlist()); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + XmlUtils.skipCurrentTag(parser); + } + } break; case "oem-permissions": { if (allowOemPermissions) { readOemPermissions(parser); @@ -1655,6 +1690,12 @@ public class SystemConfig { readPermissionAllowlist(parser, allowlist, "privapp-permissions"); } + private void readSignatureAppPermissions(@NonNull XmlPullParser parser, + @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist) + throws IOException, XmlPullParserException { + readPermissionAllowlist(parser, allowlist, "signature-permissions"); + } + private void readInstallInUserType(XmlPullParser parser, Map<String, Set<String>> doInstallMap, Map<String, Set<String>> nonInstallMap) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c9bd0b4c4648..86894fd9b405 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4830,11 +4830,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!mConstants.mEnableWaitForFinishAttachApplication) { finishAttachApplicationInner(startSeq, callingUid, pid); } - - // Temporarily disable sending BOOT_COMPLETED to see if this was impacting perf tests - if (false) { - maybeSendBootCompletedLocked(app); - } + maybeSendBootCompletedLocked(app); } catch (Exception e) { // We need kill the process group here. (b/148588589) Slog.wtf(TAG, "Exception thrown during bind of " + app, e); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c96c2ff4f2eb..3487ae3c6e6c 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -151,6 +151,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -653,7 +654,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub try { future.get(); return; - } catch (ExecutionException e) { + } catch (ExecutionException | CancellationException e) { return; } catch (InterruptedException e) { // Keep looping diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4cbee2b89bb2..9f7c07e0af35 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -18,9 +18,6 @@ package com.android.server.audio; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; -import static android.media.audio.Flags.autoPublicVolumeApiHardening; -import static android.media.audio.Flags.automaticBtDeviceType; -import static android.media.audio.Flags.focusFreezeTestApi; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; @@ -33,6 +30,9 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; +import static android.media.audio.Flags.autoPublicVolumeApiHardening; +import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.focusFreezeTestApi; import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.INVALID_UID; @@ -116,7 +116,6 @@ import android.media.AudioProfile; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; -import android.media.AudioTrack; import android.media.BluetoothProfileConnectionInfo; import android.media.FadeManagerConfiguration; import android.media.IAudioDeviceVolumeDispatcher; @@ -144,7 +143,7 @@ import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; -import android.media.LoudnessCodecConfigurator; +import android.media.LoudnessCodecController; import android.media.LoudnessCodecInfo; import android.media.MediaCodec; import android.media.MediaMetrics; @@ -10737,34 +10736,35 @@ public class AudioService extends IAudioService.Stub mLoudnessCodecHelper.unregisterLoudnessCodecUpdatesDispatcher(dispatcher); } - /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */ + /** @see LoudnessCodecController#create(int) */ @Override - public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) { - mLoudnessCodecHelper.startLoudnessCodecUpdates(piid, codecInfoList); + public void startLoudnessCodecUpdates(int sessionId) { + mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId); } - /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */ + /** @see LoudnessCodecController#release() */ @Override - public void stopLoudnessCodecUpdates(int piid) { - mLoudnessCodecHelper.stopLoudnessCodecUpdates(piid); + public void stopLoudnessCodecUpdates(int sessionId) { + mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId); } - /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */ + /** @see LoudnessCodecController#addMediaCodec(MediaCodec) */ @Override - public void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo codecInfo) { - mLoudnessCodecHelper.addLoudnessCodecInfo(piid, mediaCodecHash, codecInfo); + public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash, + LoudnessCodecInfo codecInfo) { + mLoudnessCodecHelper.addLoudnessCodecInfo(sessionId, mediaCodecHash, codecInfo); } - /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */ + /** @see LoudnessCodecController#removeMediaCodec(MediaCodec) */ @Override - public void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) { - mLoudnessCodecHelper.removeLoudnessCodecInfo(piid, codecInfo); + public void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) { + mLoudnessCodecHelper.removeLoudnessCodecInfo(sessionId, codecInfo); } - /** @see LoudnessCodecConfigurator#getLoudnessCodecParams(AudioTrack, MediaCodec) */ + /** @see LoudnessCodecController#getLoudnessCodecParams(MediaCodec) */ @Override - public PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) { - return mLoudnessCodecHelper.getLoudnessParams(piid, codecInfo); + public PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) { + return mLoudnessCodecHelper.getLoudnessParams(codecInfo); } //========================================================================================== diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java index 9b0afc4282a2..01f770b1e89f 100644 --- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java +++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java @@ -30,6 +30,8 @@ import static android.media.audio.Flags.automaticBtDeviceType; import android.annotation.IntDef; import android.annotation.NonNull; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioPlaybackConfiguration; @@ -44,7 +46,6 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemProperties; import android.util.Log; -import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; @@ -59,7 +60,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -75,9 +78,9 @@ public class LoudnessCodecHelper { /** * Property containing a string to set for a custom built in speaker SPL range as defined by * CTA2075. The options that can be set are: - * - "small": for max SPL with test signal < 75 dB, - * - "medium": for max SPL with test signal between 70 and 90 dB, - * - "large": for max SPL with test signal > 85 dB. + * - "small": for max SPL with test signal < 75 dB, + * - "medium": for max SPL with test signal between 70 and 90 dB, + * - "large": for max SPL with test signal > 85 dB. */ private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE = "audio.loudness.builtin-speaker-spl-range-size"; @@ -99,11 +102,13 @@ public class LoudnessCodecHelper { SPL_RANGE_LARGE }) @Retention(RetentionPolicy.SOURCE) - public @interface DeviceSplRange {} + public @interface DeviceSplRange { + } private static final class LoudnessRemoteCallbackList extends RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> { private final LoudnessCodecHelper mLoudnessCodecHelper; + LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) { mLoudnessCodecHelper = loudnessCodecHelper; } @@ -133,9 +138,15 @@ public class LoudnessCodecHelper { private final Object mLock = new Object(); - /** Contains for each started piid the set corresponding to unique registered audio codecs. */ + /** Contains for each started track id the known started piids. */ + @GuardedBy("mLock") + private final HashMap<LoudnessTrackId, Set<Integer>> mStartedConfigPiids = + new HashMap<>(); + + /** Contains for each LoudnessTrackId a set of started coudec infos. */ @GuardedBy("mLock") - private final SparseArray<Set<LoudnessCodecInfo>> mStartedPiids = new SparseArray<>(); + private final HashMap<LoudnessTrackId, Set<LoudnessCodecInfo>> mStartedConfigInfo = + new HashMap<>(); /** Contains the current device id assignment for each piid. */ @GuardedBy("mLock") @@ -169,10 +180,12 @@ public class LoudnessCodecHelper { mMetadataType = metadataType; return this; } + Builder setIsDownmixing(boolean isDownmixing) { mIsDownmixing = isDownmixing; return this; } + Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) { mDeviceSplRange = deviceSplRange; return this; @@ -185,8 +198,8 @@ public class LoudnessCodecHelper { } private LoudnessCodecInputProperties(int metadataType, - boolean isDownmixing, - @DeviceSplRange int deviceSplRange) { + boolean isDownmixing, + @DeviceSplRange int deviceSplRange) { mMetadataType = metadataType; mIsDownmixing = isDownmixing; mDeviceSplRange = deviceSplRange; @@ -273,6 +286,50 @@ public class LoudnessCodecHelper { } } + /** + * Contains the properties necessary to identify the tracks that are receiving annotated + * loudness data. + **/ + @VisibleForTesting + static final class LoudnessTrackId { + private final int mSessionId; + + private final int mPid; + + private LoudnessTrackId(int sessionId, int pid) { + mSessionId = sessionId; + mPid = pid; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + // type check and cast + if (getClass() != obj.getClass()) { + return false; + } + final LoudnessTrackId lti = (LoudnessTrackId) obj; + return mSessionId == lti.mSessionId && mPid == lti.mPid; + } + + @Override + public int hashCode() { + return Objects.hash(mSessionId, mPid); + } + + @Override + public String toString() { + return "Loudness track id:" + + " session ID: " + mSessionId + + " pid: " + mPid; + } + } + @GuardedBy("mLock") private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties = new HashMap<>(); @@ -290,120 +347,160 @@ public class LoudnessCodecHelper { mLoudnessUpdateDispatchers.unregister(dispatcher); } - void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) { + void startLoudnessCodecUpdates(int sessionId) { + int pid = Binder.getCallingPid(); if (DEBUG) { - Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList); + Log.d(TAG, + "startLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid); } + final LoudnessTrackId newConfig = new LoudnessTrackId(sessionId, pid); + HashSet<Integer> newPiids; synchronized (mLock) { - if (mStartedPiids.contains(piid)) { - Log.w(TAG, "Already started loudness updates for piid " + piid); + if (mStartedConfigInfo.containsKey(newConfig)) { + Log.w(TAG, "Already started loudness updates for config: " + newConfig); return; } - Set<LoudnessCodecInfo> infoSet = new HashSet<>(codecInfoList); - mStartedPiids.put(piid, infoSet); - - int pid = Binder.getCallingPid(); - mPiidToPidCache.put(piid, pid); - sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid)); + mStartedConfigInfo.put(newConfig, new HashSet<>()); + newPiids = new HashSet<>(); + mStartedConfigPiids.put(newConfig, newPiids); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mAudioService.getActivePlaybackConfigurations().stream().filter( - conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent( - this::updateCodecParametersForConfiguration); + conf -> conf.getSessionId() == sessionId + && conf.getClientPid() == pid).forEach(apc -> { + int piid = apc.getPlayerInterfaceId(); + synchronized (mLock) { + newPiids.add(piid); + mPiidToPidCache.put(piid, pid); + sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid)); + } + }); } } - void stopLoudnessCodecUpdates(int piid) { + void stopLoudnessCodecUpdates(int sessionId) { + int pid = Binder.getCallingPid(); if (DEBUG) { - Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid); + Log.d(TAG, + "stopLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid); } + final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid); synchronized (mLock) { - if (!mStartedPiids.contains(piid)) { - Log.w(TAG, "Loudness updates are already stopped for piid " + piid); + if (!mStartedConfigInfo.containsKey(config)) { + Log.w(TAG, "Loudness updates are already stopped config: " + config); return; } - mStartedPiids.remove(piid); - sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1))); - mPiidToDeviceIdCache.delete(piid); - mPiidToPidCache.delete(piid); + final Set<Integer> startedPiidSet = mStartedConfigPiids.get(config); + if (startedPiidSet == null) { + Log.e(TAG, "Loudness updates are already stopped config: " + config); + return; + } + for (Integer piid : startedPiidSet) { + sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1))); + mPiidToDeviceIdCache.delete(piid); + mPiidToPidCache.delete(piid); + } + mStartedConfigPiids.remove(config); + mStartedConfigInfo.remove(config); } } - void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo info) { + void addLoudnessCodecInfo(int sessionId, int mediaCodecHash, + LoudnessCodecInfo info) { + int pid = Binder.getCallingPid(); if (DEBUG) { - Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " mcHash " + mediaCodecHash + " info " - + info); + Log.d(TAG, "addLoudnessCodecInfo: sessionId " + sessionId + + " mcHash " + mediaCodecHash + " info " + info + " pid " + pid); } + final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid); Set<LoudnessCodecInfo> infoSet; + Set<Integer> piids; synchronized (mLock) { - if (!mStartedPiids.contains(piid)) { - Log.w(TAG, "Cannot add new loudness info for stopped piid " + piid); + if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey( + config)) { + Log.w(TAG, "Cannot add new loudness info for stopped config " + config); return; } - infoSet = mStartedPiids.get(piid); + piids = mStartedConfigPiids.get(config); + infoSet = mStartedConfigInfo.get(config); infoSet.add(info); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - mAudioService.getActivePlaybackConfigurations().stream().filter( - conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent( - apc -> { - final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); - if (deviceInfo != null) { - PersistableBundle updateBundle = new PersistableBundle(); - synchronized (mLock) { - updateBundle.putPersistableBundle( - Integer.toString(mediaCodecHash), - getCodecBundle_l(deviceInfo, info)); - } - if (!updateBundle.isDefinitelyEmpty()) { - dispatchNewLoudnessParameters(piid, updateBundle); - } - } - }); + final PersistableBundle updateBundle = new PersistableBundle(); + Optional<AudioPlaybackConfiguration> apc = + mAudioService.getActivePlaybackConfigurations().stream().filter( + conf -> conf.getSessionId() == sessionId + && conf.getClientPid() == pid).findFirst(); + if (apc.isEmpty()) { + if (DEBUG) { + Log.d(TAG, + "No APCs found when adding loudness codec info. Using AudioAttributes" + + " routing for initial update"); + } + updateBundle.putPersistableBundle(Integer.toString(mediaCodecHash), + getLoudnessParams(info)); + } else { + final AudioDeviceInfo deviceInfo = apc.get().getAudioDeviceInfo(); + if (deviceInfo != null) { + synchronized (mLock) { + // found a piid that matches the configuration + piids.add(apc.get().getPlayerInterfaceId()); + + updateBundle.putPersistableBundle( + Integer.toString(mediaCodecHash), + getCodecBundle_l(deviceInfo.getInternalType(), + deviceInfo.getAddress(), info)); + } + } + } + if (!updateBundle.isDefinitelyEmpty()) { + dispatchNewLoudnessParameters(sessionId, updateBundle); + } } } - void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) { + void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) { if (DEBUG) { - Log.d(TAG, "removeLoudnessCodecInfo: piid " + piid + " info " + codecInfo); + Log.d(TAG, "removeLoudnessCodecInfo: session ID" + sessionId + " info " + codecInfo); } + + int pid = Binder.getCallingPid(); + final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid); synchronized (mLock) { - if (!mStartedPiids.contains(piid)) { - Log.w(TAG, "Cannot remove loudness info for stopped piid " + piid); + if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey( + config)) { + Log.w(TAG, "Cannot remove loudness info for stopped config " + config); return; } - final Set<LoudnessCodecInfo> infoSet = mStartedPiids.get(piid); - infoSet.remove(codecInfo); + final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config); + if (!codecInfos.remove(codecInfo)) { + Log.w(TAG, "Could not find to remove codecInfo " + codecInfo); + } } } - PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) { + PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) { if (DEBUG) { - Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo); - } - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - final List<AudioPlaybackConfiguration> configs = - mAudioService.getActivePlaybackConfigurations(); - - for (final AudioPlaybackConfiguration apc : configs) { - if (apc.getPlayerInterfaceId() == piid) { - final AudioDeviceInfo info = apc.getAudioDeviceInfo(); - if (info == null) { - Log.i(TAG, "Player with piid " + piid + " is not assigned any device"); - break; - } - synchronized (mLock) { - return getCodecBundle_l(info, codecInfo); - } - } + Log.d(TAG, "getLoudnessParams: codecInfo " + codecInfo); + } + final ArrayList<AudioDeviceAttributes> devicesForAttributes = + mAudioService.getDevicesForAttributesInt(new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(), /*forVolume=*/false); + if (!devicesForAttributes.isEmpty()) { + final AudioDeviceAttributes audioDeviceAttribute = devicesForAttributes.get(0); + synchronized (mLock) { + return getCodecBundle_l(audioDeviceAttribute.getInternalType(), + audioDeviceAttribute.getAddress(), codecInfo); } } @@ -444,13 +541,21 @@ public class LoudnessCodecHelper { continue; } mPiidToDeviceIdCache.put(piid, deviceInfo.getId()); - if (mStartedPiids.contains(piid)) { + final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(), + apc.getClientPid()); + if (mStartedConfigInfo.containsKey(config) && mStartedConfigPiids.containsKey( + config)) { + if (DEBUG) { + Log.d(TAG, "Updating config: " + config + " with APC " + apc); + } updateApcList.add(apc); + // update the started piid set + mStartedConfigPiids.get(config).add(piid); } } } - updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc)); + updateApcList.forEach(this::updateCodecParametersForConfiguration); } /** Updates and dispatches the new loudness parameters for all its registered codecs. */ @@ -458,13 +563,18 @@ public class LoudnessCodecHelper { // Registered clients pw.println("\nRegistered clients:\n"); synchronized (mLock) { - for (int i = 0; i < mStartedPiids.size(); ++i) { - int piid = mStartedPiids.keyAt(i); - int pid = mPiidToPidCache.get(piid, -1); - final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid); - pw.println(String.format("Player piid %d pid %d active codec types %s\n", piid, - pid, codecInfos.stream().map(Object::toString).collect( - Collectors.joining(", ")))); + for (Map.Entry<LoudnessTrackId, Set<Integer>> entry : mStartedConfigPiids.entrySet()) { + for (Integer piid : entry.getValue()) { + int pid = mPiidToPidCache.get(piid, -1); + final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get( + entry.getKey()); + if (codecInfos != null) { + pw.println( + String.format("Player piid %d pid %d active codec types %s\n", piid, + pid, codecInfos.stream().map(Object::toString).collect( + Collectors.joining(", ")))); + } + } } pw.println(); } @@ -481,10 +591,12 @@ public class LoudnessCodecHelper { if (DEBUG) { Log.d(TAG, "Removing piid " + piid); } - mStartedPiids.delete(piid); mPiidToDeviceIdCache.delete(piid); } } + + mStartedConfigPiids.entrySet().removeIf(entry -> entry.getKey().mPid == pid); + mStartedConfigInfo.entrySet().removeIf(entry -> entry.getKey().mPid == pid); } } @@ -499,46 +611,55 @@ public class LoudnessCodecHelper { } final PersistableBundle allBundles = new PersistableBundle(); - final int piid = apc.getPlayerInterfaceId(); synchronized (mLock) { - final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid); - + final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(), + apc.getClientPid()); + final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config); final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); + if (codecInfos != null && deviceInfo != null) { for (LoudnessCodecInfo info : codecInfos) { - allBundles.putPersistableBundle(Integer.toString(info.hashCode()), - getCodecBundle_l(deviceInfo, info)); + if (info != null) { + allBundles.putPersistableBundle(Integer.toString(info.hashCode()), + getCodecBundle_l(deviceInfo.getInternalType(), + deviceInfo.getAddress(), info)); + } } } } if (!allBundles.isDefinitelyEmpty()) { - dispatchNewLoudnessParameters(piid, allBundles); + dispatchNewLoudnessParameters(apc.getSessionId(), allBundles); } } - private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) { + private void dispatchNewLoudnessParameters(int sessionId, + PersistableBundle bundle) { if (DEBUG) { - Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid + " bundle: " + bundle); + Log.d(TAG, + "dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle); } final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; ++i) { try { mLoudnessUpdateDispatchers.getBroadcastItem(i) - .dispatchLoudnessCodecParameterChange(piid, bundle); + .dispatchLoudnessCodecParameterChange(sessionId, bundle); } catch (RemoteException e) { - Log.e(TAG, "Error dispatching for piid: " + piid + " bundle: " + bundle , e); + Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle, + e); } } mLoudnessUpdateDispatchers.finishBroadcast(); } @GuardedBy("mLock") - private PersistableBundle getCodecBundle_l(AudioDeviceInfo deviceInfo, - LoudnessCodecInfo codecInfo) { + private PersistableBundle getCodecBundle_l(int internalDeviceType, + String address, + LoudnessCodecInfo codecInfo) { LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder(); - LoudnessCodecInputProperties prop = builder.setDeviceSplRange(getDeviceSplRange(deviceInfo)) + LoudnessCodecInputProperties prop = builder.setDeviceSplRange( + getDeviceSplRange(internalDeviceType, address)) .setIsDownmixing(codecInfo.isDownmixing) .setMetadataType(codecInfo.metadataType) .build(); @@ -552,14 +673,15 @@ public class LoudnessCodecHelper { } @DeviceSplRange - private int getDeviceSplRange(AudioDeviceInfo deviceInfo) { - final int internalDeviceType = deviceInfo.getInternalType(); - final @AudioDeviceCategory int deviceCategory; - if (automaticBtDeviceType()) { - deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress()); - } else { - deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy( - deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType)); + private int getDeviceSplRange(int internalDeviceType, String address) { + @AudioDeviceCategory int deviceCategory; + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + if (automaticBtDeviceType()) { + deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(address); + } else { + deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy( + address, AudioSystem.isBluetoothLeDevice(internalDeviceType)); + } } if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) { final String splRange = SystemProperties.get( @@ -595,20 +717,28 @@ public class LoudnessCodecHelper { private static String splRangeToString(@DeviceSplRange int splRange) { switch (splRange) { - case SPL_RANGE_LARGE: return "large"; - case SPL_RANGE_MEDIUM: return "medium"; - case SPL_RANGE_SMALL: return "small"; - default: return "unknown"; + case SPL_RANGE_LARGE: + return "large"; + case SPL_RANGE_MEDIUM: + return "medium"; + case SPL_RANGE_SMALL: + return "small"; + default: + return "unknown"; } } @DeviceSplRange private static int stringToSplRange(String splRange) { switch (splRange) { - case "large": return SPL_RANGE_LARGE; - case "medium": return SPL_RANGE_MEDIUM; - case "small": return SPL_RANGE_SMALL; - default: return SPL_RANGE_UNKNOWN; + case "large": + return SPL_RANGE_LARGE; + case "medium": + return SPL_RANGE_MEDIUM; + case "small": + return SPL_RANGE_SMALL; + default: + return SPL_RANGE_UNKNOWN; } } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java index f8a98675c0c7..7f04628ff6de 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -87,10 +87,30 @@ public interface BiometricContext { * * @param context context that will be modified when changed * @param consumer callback when the context is modified + * + * @deprecated instead use {@link BiometricContext#subscribe(OperationContextExt, Consumer, + * Consumer, AuthenticateOptions)} + * TODO (b/294161627): Delete this API once Flags.DE_HIDL is removed. */ + @Deprecated void subscribe(@NonNull OperationContextExt context, @NonNull Consumer<OperationContext> consumer); + /** + * Subscribe to context changes and start the HAL operation. + * + * Note that this method only notifies for properties that are visible to the HAL. + * + * @param context context that will be modified when changed + * @param startHalConsumer callback to start HAL operation after subscription is done + * @param updateContextConsumer callback when the context is modified + * @param options authentication options for updating the context + */ + void subscribe(@NonNull OperationContextExt context, + @NonNull Consumer<OperationContext> startHalConsumer, + @NonNull Consumer<OperationContext> updateContextConsumer, + @Nullable AuthenticateOptions options); + /** Unsubscribe from context changes. */ void unsubscribe(@NonNull OperationContextExt context); diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 535b7b743625..d8dfa60bdb51 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -238,6 +238,19 @@ public final class BiometricContextProvider implements BiometricContext { } @Override + public void subscribe(@NonNull OperationContextExt context, + @NonNull Consumer<OperationContext> startHalConsumer, + @NonNull Consumer<OperationContext> updateContextConsumer, + @Nullable AuthenticateOptions options) { + mSubscribers.put(updateContext(context, context.isCrypto()), updateContextConsumer); + if (options != null) { + startHalConsumer.accept(context.toAidlContext(options)); + } else { + startHalConsumer.accept(context.toAidlContext()); + } + } + + @Override public void unsubscribe(@NonNull OperationContextExt context) { mSubscribers.remove(context); } diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java index 0045d44af9a1..da4e5154d49e 100644 --- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java +++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java @@ -102,6 +102,24 @@ public class OperationContextExt { * @return the underlying AIDL context */ @NonNull + public OperationContext toAidlContext(@NonNull AuthenticateOptions options) { + if (options instanceof FaceAuthenticateOptions) { + return toAidlContext((FaceAuthenticateOptions) options); + } + if (options instanceof FingerprintAuthenticateOptions) { + return toAidlContext((FingerprintAuthenticateOptions) options); + } + throw new IllegalStateException("Authenticate options are invalid."); + } + + /** + * Gets the subset of the context that can be shared with the HAL and updates + * it with the given options. + * + * @param options authenticate options + * @return the underlying AIDL context + */ + @NonNull public OperationContext toAidlContext(@NonNull FaceAuthenticateOptions options) { mAidlContext.authenticateReason = AuthenticateReason .faceAuthenticateReason(getAuthReason(options)); diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java index 0f01510bd908..b0649b90c466 100644 --- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.os.IBinder; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; @@ -93,6 +94,9 @@ public abstract class HalClientMonitor<T> extends BaseClientMonitor { } protected OperationContextExt getOperationContext() { + if (Flags.deHidl()) { + return mOperationContext; + } return getBiometricContext().updateContext(mOperationContext, isCryptoOperation()); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 470dc4b7172c..22e399c6747b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -37,6 +37,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -153,7 +154,11 @@ public class FaceAuthenticationClient 0 /* vendorCode */); mCallback.onClientFinished(this, false /* success */); } else { - mCancellationSignal = doAuthenticate(); + if (Flags.deHidl()) { + startAuthenticate(); + } else { + mCancellationSignal = doAuthenticate(); + } } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); @@ -182,6 +187,33 @@ public class FaceAuthenticationClient } } + private void startAuthenticate() throws RemoteException { + final AidlSession session = getFreshDaemon(); + + if (session.hasContextMethods()) { + final OperationContextExt opContext = getOperationContext(); + getBiometricContext().subscribe(opContext, ctx -> { + try { + mCancellationSignal = session.getSession().authenticateWithContext( + mOperationId, ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting auth", e); + onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } + }, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }, getOptions()); + } else { + mCancellationSignal = session.getSession().authenticate(mOperationId); + } + } + @Override protected void stopHalOperation() { unsubscribeBiometricContext(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index a529fb9779a7..5ddddda4e201 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -29,6 +29,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; @@ -111,7 +112,11 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements } try { - mCancellationSignal = doDetectInteraction(); + if (Flags.deHidl()) { + startDetect(); + } else { + mCancellationSignal = doDetectInteraction(); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting face detect", e); mCallback.onClientFinished(this, false /* success */); @@ -138,6 +143,30 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements } } + private void startDetect() throws RemoteException { + final AidlSession session = getFreshDaemon(); + + if (session.hasContextMethods()) { + final OperationContextExt opContext = getOperationContext(); + getBiometricContext().subscribe(opContext, ctx -> { + try { + mCancellationSignal = session.getSession().detectInteractionWithContext(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting face detect", e); + mCallback.onClientFinished(this, false /* success */); + } + }, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }, mOptions); + } else { + mCancellationSignal = session.getSession().detectInteraction(); + } + } + @Override public void onInteractionDetected() { vibrateSuccess(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index 0af6e40434ef..f5c452992674 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -36,6 +36,7 @@ import android.util.Slog; import android.view.Surface; import com.android.internal.R; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; @@ -187,7 +188,11 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { features[i] = featureList.get(i); } - mCancellationSignal = doEnroll(features); + if (Flags.deHidl()) { + startEnroll(features); + } else { + mCancellationSignal = doEnroll(features); + } } catch (RemoteException | IllegalArgumentException e) { Slog.e(TAG, "Exception when requesting enroll", e); onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); @@ -231,6 +236,48 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { } } + private void startEnroll(byte[] features) throws RemoteException { + final AidlSession session = getFreshDaemon(); + final HardwareAuthToken hat = + HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken); + + if (session.hasContextMethods()) { + final OperationContextExt opContext = getOperationContext(); + getBiometricContext().subscribe(opContext, ctx -> { + try { + if (session.supportsFaceEnrollOptions()) { + FaceEnrollOptions options = new FaceEnrollOptions(); + options.hardwareAuthToken = hat; + options.enrollmentType = EnrollmentType.DEFAULT; + options.features = features; + options.nativeHandlePreview = null; + options.context = ctx; + options.surfacePreview = mPreviewSurface; + mCancellationSignal = session.getSession().enrollWithOptions(options); + } else { + mCancellationSignal = session.getSession().enrollWithContext( + hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, ctx); + } + } catch (RemoteException e) { + Slog.e(TAG, "Exception when requesting enroll", e); + onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } + }, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }, null /* options */); + } else { + mCancellationSignal = session.getSession().enroll(hat, EnrollmentType.DEFAULT, features, + mHwPreviewHandle); + } + } + + @Override protected void stopHalOperation() { unsubscribeBiometricContext(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 145885de5c32..f7e812330ece 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -44,6 +44,7 @@ import android.provider.Settings; import android.util.Slog; import com.android.internal.R; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -297,7 +298,11 @@ public class FingerprintAuthenticationClient } try { - mCancellationSignal = doAuthenticate(); + if (Flags.deHidl()) { + startAuthentication(); + } else { + mCancellationSignal = doAuthenticate(); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); onError( @@ -353,6 +358,51 @@ public class FingerprintAuthenticationClient return cancel; } + private void startAuthentication() { + final AidlSession session = getFreshDaemon(); + final OperationContextExt opContext = getOperationContext(); + + getBiometricContext().subscribe(opContext, ctx -> { + try { + if (session.hasContextMethods()) { + mCancellationSignal = session.getSession().authenticateWithContext(mOperationId, + ctx); + } else { + mCancellationSignal = session.getSession().authenticate(mOperationId); + } + + if (getBiometricContext().isAwake()) { + mALSProbeCallback.getProbe().enable(); + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + onError( + BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mSensorOverlays.hide(getSensorId()); + if (sidefpsControllerRefactor()) { + mAuthenticationStateListeners.onAuthenticationStopped(); + } + mCallback.onClientFinished(this, false /* success */); + } + }, ctx -> { + if (session.hasContextMethods()) { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + } + + final boolean isAwake = getBiometricContext().isAwake(); + if (isAwake) { + mALSProbeCallback.getProbe().enable(); + } else { + mALSProbeCallback.getProbe().disable(); + } + }, getOptions()); + } + @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 3aab7b300a87..a7fb7741fee1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -31,6 +31,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; @@ -105,7 +106,11 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> this); try { - mCancellationSignal = doDetectInteraction(); + if (Flags.deHidl()) { + startDetectInteraction(); + } else { + mCancellationSignal = doDetectInteraction(); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting finger detect", e); mSensorOverlays.hide(getSensorId()); @@ -139,6 +144,32 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> } } + private void startDetectInteraction() throws RemoteException { + final AidlSession session = getFreshDaemon(); + + if (session.hasContextMethods()) { + final OperationContextExt opContext = getOperationContext(); + getBiometricContext().subscribe(opContext, ctx -> { + try { + mCancellationSignal = session.getSession().detectInteractionWithContext( + ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to start detect interaction", e); + mSensorOverlays.hide(getSensorId()); + mCallback.onClientFinished(this, false /* success */); + } + }, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }, mOptions); + } else { + mCancellationSignal = session.getSession().detectInteraction(); + } + } + @Override public void onInteractionDetected() { vibrateSuccess(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index bf5011de1e59..3fb9223249b4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -39,6 +39,7 @@ import android.os.RemoteException; import android.util.Slog; import android.view.accessibility.AccessibilityManager; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -200,7 +201,11 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { - mCancellationSignal = doEnroll(); + if (Flags.deHidl()) { + startEnroll(); + } else { + mCancellationSignal = doEnroll(); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting enroll", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS, @@ -237,6 +242,35 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement } } + private void startEnroll() throws RemoteException { + final AidlSession session = getFreshDaemon(); + final HardwareAuthToken hat = + HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken); + + if (session.hasContextMethods()) { + final OperationContextExt opContext = getOperationContext(); + getBiometricContext().subscribe(opContext, ctx -> { + try { + mCancellationSignal = session.getSession().enrollWithContext( + hat, ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting enroll", e); + onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } + }, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }, null /* options */); + } else { + mCancellationSignal = session.getSession().enroll(hat); + } + } + @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index fb4943a9f4ca..67c23fc4db12 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1328,8 +1328,7 @@ public class InputManagerService extends IInputManager.Stub mPointerIconDisplayContext = null; } - updateAdditionalDisplayInputProperties(displayId, - AdditionalDisplayInputProperties::reset); + updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset); // TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been // removed in InputDispatcher instead of this callback. @@ -1812,8 +1811,6 @@ public class InputManagerService extends IInputManager.Stub mPointerIconType = icon.getType(); mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null; - if (!mCurrentDisplayProperties.pointerIconVisible) return false; - return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken); } } @@ -3478,6 +3475,10 @@ public class InputManagerService extends IInputManager.Stub private void applyAdditionalDisplayInputPropertiesLocked( AdditionalDisplayInputProperties properties) { // Handle changes to each of the individual properties. + // TODO(b/293587049): This approach for updating pointer display properties is only for when + // PointerChoreographer is disabled. Remove this logic when PointerChoreographer is + // permanently enabled. + if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) { mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible; if (properties.pointerIconVisible) { @@ -3496,7 +3497,6 @@ public class InputManagerService extends IInputManager.Stub != mCurrentDisplayProperties.mousePointerAccelerationEnabled) { mCurrentDisplayProperties.mousePointerAccelerationEnabled = properties.mousePointerAccelerationEnabled; - mNative.setMousePointerAccelerationEnabled(properties.mousePointerAccelerationEnabled); } } @@ -3509,7 +3509,16 @@ public class InputManagerService extends IInputManager.Stub properties = new AdditionalDisplayInputProperties(); mAdditionalDisplayInputProperties.put(displayId, properties); } + final boolean oldPointerIconVisible = properties.pointerIconVisible; + final boolean oldMouseAccelerationEnabled = properties.mousePointerAccelerationEnabled; updater.accept(properties); + if (oldPointerIconVisible != properties.pointerIconVisible) { + mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible); + } + if (oldMouseAccelerationEnabled != properties.mousePointerAccelerationEnabled) { + mNative.setMousePointerAccelerationEnabled(displayId, + properties.mousePointerAccelerationEnabled); + } if (properties.allDefaults()) { mAdditionalDisplayInputProperties.remove(displayId); } diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index c3ef80f3624a..6f5202029998 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -119,7 +119,7 @@ interface NativeInputManagerService { void setPointerSpeed(int speed); - void setMousePointerAccelerationEnabled(boolean enabled); + void setMousePointerAccelerationEnabled(int displayId, boolean enabled); void setTouchpadPointerSpeed(int speed); @@ -190,6 +190,8 @@ interface NativeInputManagerService { boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId, @NonNull IBinder inputToken); + void setPointerIconVisibility(int displayId, boolean visible); + void requestPointerCapture(IBinder windowToken, boolean enabled); boolean canDispatchToDisplay(int deviceId, int displayId); @@ -352,7 +354,7 @@ interface NativeInputManagerService { public native void setPointerSpeed(int speed); @Override - public native void setMousePointerAccelerationEnabled(boolean enabled); + public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled); @Override public native void setTouchpadPointerSpeed(int speed); @@ -452,6 +454,9 @@ interface NativeInputManagerService { int pointerId, IBinder inputToken); @Override + public native void setPointerIconVisibility(int displayId, boolean visible); + + @Override public native void requestPointerCapture(IBinder windowToken, boolean enabled); @Override diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 9c4225dc2542..39df5be0f2fa 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -1384,7 +1384,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements try { reportLocation(LocationResult.wrap(location).validate()); } catch (BadLocationException e) { - throw new IllegalArgumentException(e); + Log.e(TAG, "Dropping invalid location: " + e); + return; } if (mStarted) { @@ -1759,7 +1760,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements try { reportLocation(LocationResult.wrap(locations).validate()); } catch (BadLocationException e) { - throw new IllegalArgumentException(e); + Log.e(TAG, "Dropping invalid locations: " + e); + return; } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 28a1c7ad0540..85a131579497 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -2336,6 +2336,11 @@ class MediaRouter2ServiceImpl { mSystemProvider.getDefaultRoute()); } + private static String getPackageNameFromNullableRecord( + @Nullable RouterRecord routerRecord) { + return routerRecord != null ? routerRecord.mPackageName : "<null router record>"; + } + private static String toLoggingMessage( String source, String providerId, ArrayList<MediaRoute2Info> routes) { String routesString = @@ -2573,8 +2578,17 @@ class MediaRouter2ServiceImpl { RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId); if (matchingRecord != routerRecord) { - Slog.w(TAG, "Ignoring " + description + " route from non-matching router. " - + "packageName=" + routerRecord.mPackageName + " route=" + route); + Slog.w( + TAG, + "Ignoring " + + description + + " route from non-matching router." + + " routerRecordPackageName=" + + getPackageNameFromNullableRecord(routerRecord) + + " matchingRecordPackageName=" + + getPackageNameFromNullableRecord(matchingRecord) + + " route=" + + route); return false; } @@ -2613,9 +2627,15 @@ class MediaRouter2ServiceImpl { @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) { final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId); if (matchingRecord != routerRecord) { - Slog.w(TAG, "Ignoring releasing session from non-matching router. packageName=" - + (routerRecord == null ? null : routerRecord.mPackageName) - + " uniqueSessionId=" + uniqueSessionId); + Slog.w( + TAG, + "Ignoring releasing session from non-matching router." + + " routerRecordPackageName=" + + getPackageNameFromNullableRecord(routerRecord) + + " matchingRecordPackageName=" + + getPackageNameFromNullableRecord(matchingRecord) + + " uniqueSessionId=" + + uniqueSessionId); return; } diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index c110fb67b54f..200b17bc2f97 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -16,7 +16,12 @@ package com.android.server.pm; +import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.app.Flags; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; @@ -27,6 +32,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; +import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.Handler; @@ -69,26 +75,29 @@ public class BackgroundInstallControlService extends SystemService { private static final String DISK_FILE_NAME = "states"; private static final String DISK_DIR_NAME = "bic"; - private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10; + private static final String ENFORCE_PERMISSION_ERROR_MSG = + "User is not permitted to call service: "; + private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10; private static final int MSG_USAGE_EVENT_RECEIVED = 0; private static final int MSG_PACKAGE_ADDED = 1; private static final int MSG_PACKAGE_REMOVED = 2; private final BinderService mBinderService; private final PackageManager mPackageManager; + // TODO migrate all internal PackageManager calls to PackageManagerInternal where possible. + // b/310983905 private final PackageManagerInternal mPackageManagerInternal; private final PermissionManagerServiceInternal mPermissionManager; private final Handler mHandler; private final File mDiskFile; - + private final Context mContext; private SparseSetArray<String> mBackgroundInstalledPackages = null; // User ID -> package name -> set of foreground time frame - private final SparseArrayMap<String, - TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames = - new SparseArrayMap<>(); + private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> + mInstallerForegroundTimeFrames = new SparseArrayMap<>(); public BackgroundInstallControlService(@NonNull Context context) { this(new InjectorImpl(context)); @@ -102,15 +111,13 @@ public class BackgroundInstallControlService extends SystemService { mPermissionManager = injector.getPermissionManager(); mHandler = new EventHandler(injector.getLooper(), this); mDiskFile = injector.getDiskFile(); + mContext = injector.getContext(); UsageStatsManagerInternal usageStatsManagerInternal = injector.getUsageStatsManagerInternal(); usageStatsManagerInternal.registerListener( (userId, event) -> - mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, - userId, - 0, - event).sendToTarget() - ); + mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, userId, 0, event) + .sendToTarget()); mBinderService = new BinderService(this); } @@ -124,12 +131,17 @@ public class BackgroundInstallControlService extends SystemService { @Override public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages( @PackageManager.PackageInfoFlagsBits long flags, int userId) { + if (Flags.bicClient()) { + mService.enforceCallerPermissions(); + } if (!Build.IS_DEBUGGABLE) { return mService.getBackgroundInstalledPackages(flags, userId); } // The debug.transparency.bg-install-apps (only works for debuggable builds) // is used to set mock list of background installed apps for testing. // The list of apps' names is delimited by ",". + // TODO: Remove after migrating test to new background install method using + // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905 String propertyString = SystemProperties.get("debug.transparency.bg-install-apps"); if (TextUtils.isEmpty(propertyString)) { return mService.getBackgroundInstalledPackages(flags, userId); @@ -137,25 +149,36 @@ public class BackgroundInstallControlService extends SystemService { return mService.getMockBackgroundInstalledPackages(propertyString); } } + + } + + @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES) + void enforceCallerPermissions() throws SecurityException { + mContext.enforceCallingOrSelfPermission(GET_BACKGROUND_INSTALLED_PACKAGES, + ENFORCE_PERMISSION_ERROR_MSG + GET_BACKGROUND_INSTALLED_PACKAGES); } @VisibleForTesting ParceledListSlice<PackageInfo> getBackgroundInstalledPackages( @PackageManager.PackageInfoFlagsBits long flags, int userId) { - List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( + final long token = Binder.clearCallingIdentity(); + try { + List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( PackageManager.PackageInfoFlags.of(flags), userId); - initBackgroundInstalledPackages(); - - ListIterator<PackageInfo> iter = packages.listIterator(); - while (iter.hasNext()) { - String packageName = iter.next().packageName; - if (!mBackgroundInstalledPackages.contains(userId, packageName)) { - iter.remove(); + initBackgroundInstalledPackages(); + ListIterator<PackageInfo> iter = packages.listIterator(); + while (iter.hasNext()) { + String packageName = iter.next().packageName; + if (!mBackgroundInstalledPackages.contains(userId, packageName)) { + iter.remove(); + } } - } - return new ParceledListSlice<>(packages); + return new ParceledListSlice<>(packages); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -168,8 +191,9 @@ public class BackgroundInstallControlService extends SystemService { List<PackageInfo> mockPackages = new ArrayList<>(); for (String name : mockPackageNames) { try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(name, - PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL)); + PackageInfo packageInfo = + mPackageManager.getPackageInfo( + name, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL)); mockPackages.add(packageInfo); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package's PackageInfo not found " + name); @@ -190,18 +214,16 @@ public class BackgroundInstallControlService extends SystemService { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_USAGE_EVENT_RECEIVED: { - mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */); + case MSG_USAGE_EVENT_RECEIVED: + mService.handleUsageEvent( + (UsageEvents.Event) msg.obj, msg.arg1 /* userId */); break; - } - case MSG_PACKAGE_ADDED: { + case MSG_PACKAGE_ADDED: mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */); break; - } - case MSG_PACKAGE_REMOVED: { + case MSG_PACKAGE_REMOVED: mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */); break; - } default: Slog.w(TAG, "Unknown message: " + msg.what); } @@ -211,8 +233,9 @@ public class BackgroundInstallControlService extends SystemService { void handlePackageAdd(String packageName, int userId) { ApplicationInfo appInfo = null; try { - appInfo = mPackageManager.getApplicationInfoAsUser(packageName, - PackageManager.ApplicationInfoFlags.of(0), userId); + appInfo = + mPackageManager.getApplicationInfoAsUser( + packageName, PackageManager.ApplicationInfoFlags.of(0), userId); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package's appInfo not found " + packageName); return; @@ -231,15 +254,18 @@ public class BackgroundInstallControlService extends SystemService { // the installers without INSTALL_PACKAGES perm can't perform // the installation in background. So we can just filter out them. - if (mPermissionManager.checkPermission(installerPackageName, - android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT, - userId) != PackageManager.PERMISSION_GRANTED) { + if (mPermissionManager.checkPermission( + installerPackageName, + android.Manifest.permission.INSTALL_PACKAGES, + Context.DEVICE_ID_DEFAULT, + userId) + != PERMISSION_GRANTED) { return; } // convert up-time to current time. - final long installTimestamp = System.currentTimeMillis() - - (SystemClock.uptimeMillis() - appInfo.createTimestamp); + final long installTimestamp = + System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp); if (installedByAdb(initiatingPackageName) || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) { @@ -257,8 +283,8 @@ public class BackgroundInstallControlService extends SystemService { return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName); } - private boolean wasForegroundInstallation(String installerPackageName, - int userId, long installTimestamp) { + private boolean wasForegroundInstallation( + String installerPackageName, int userId, long installTimestamp) { TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames = mInstallerForegroundTimeFrames.get(userId, installerPackageName); @@ -347,12 +373,12 @@ public class BackgroundInstallControlService extends SystemService { for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) { int userId = mBackgroundInstalledPackages.keyAt(i); for (String packageName : mBackgroundInstalledPackages.get(userId)) { - long token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoOutputStream.start( + BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); protoOutputStream.write( BackgroundInstalledPackageProto.PACKAGE_NAME, packageName); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, userId + 1); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, userId + 1); protoOutputStream.end(token); } } @@ -385,23 +411,28 @@ public class BackgroundInstallControlService extends SystemService { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - Slog.w(TAG, "Undefined field in proto: " - + protoInputStream.getFieldNumber()); + Slog.w( + TAG, + "Undefined field in proto: " + + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -430,9 +461,12 @@ public class BackgroundInstallControlService extends SystemService { if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) { return true; } - return mPermissionManager.checkPermission(pkgName, - android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT, - userId) == PackageManager.PERMISSION_GRANTED; + return mPermissionManager.checkPermission( + pkgName, + android.Manifest.permission.INSTALL_PACKAGES, + Context.DEVICE_ID_DEFAULT, + userId) + == PERMISSION_GRANTED; } @Override @@ -446,21 +480,22 @@ public class BackgroundInstallControlService extends SystemService { publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService); } - mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - mHandler.obtainMessage(MSG_PACKAGE_ADDED, - userId, 0, packageName).sendToTarget(); - } + mPackageManagerInternal.getPackageList( + new PackageManagerInternal.PackageListObserver() { + @Override + public void onPackageAdded(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName) + .sendToTarget(); + } - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - mHandler.obtainMessage(MSG_PACKAGE_REMOVED, - userId, 0, packageName).sendToTarget(); - } - }); + @Override + public void onPackageRemoved(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName) + .sendToTarget(); + } + }); } // The foreground time frame (ForegroundTimeFrame) represents the period @@ -516,7 +551,7 @@ public class BackgroundInstallControlService extends SystemService { } /** - * Dependency injector for {@link #BackgroundInstallControlService)}. + * Dependency injector for {@link BackgroundInstallControlService}. */ interface Injector { Context getContext(); @@ -532,6 +567,7 @@ public class BackgroundInstallControlService extends SystemService { Looper getLooper(); File getDiskFile(); + } private static final class InjectorImpl implements Injector { @@ -568,11 +604,11 @@ public class BackgroundInstallControlService extends SystemService { @Override public Looper getLooper() { - ServiceThread serviceThread = new ServiceThread(TAG, - android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); + ServiceThread serviceThread = + new ServiceThread( + TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); serviceThread.start(); return serviceThread.getLooper(); - } @Override diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index ac826afc1d22..68c95b16d318 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -18,6 +18,10 @@ package com.android.server.pm; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY; +import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static android.app.PendingIntent.FLAG_IMMUTABLE; @@ -43,6 +47,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.admin.DevicePolicyCache; @@ -213,6 +218,7 @@ public class LauncherAppsService extends SystemService { private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final ShortcutServiceInternal mShortcutServiceInternal; private final PackageManagerInternal mPackageManagerInternal; + private final AppOpsManager mAppOpsManager; private final PackageCallbackList<IOnAppsChangedListener> mListeners = new PackageCallbackList<IOnAppsChangedListener>(); private final DevicePolicyManager mDpm; @@ -253,6 +259,7 @@ public class LauncherAppsService extends SystemService { LocalServices.getService(ShortcutServiceInternal.class)); mPackageManagerInternal = Objects.requireNonNull( LocalServices.getService(PackageManagerInternal.class)); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mShortcutServiceInternal.addListener(mPackageMonitor); mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal); mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler); @@ -1998,6 +2005,23 @@ public class LauncherAppsService extends SystemService { } } + @Override + public void setArchiveCompatibilityOptions(boolean enableIconOverlay, + boolean enableUnarchivalConfirmation) { + int callingUid = Binder.getCallingUid(); + Binder.withCleanCallingIdentity( + () -> { + mAppOpsManager.setUidMode( + OP_ARCHIVE_ICON_OVERLAY, + callingUid, + enableIconOverlay ? MODE_ALLOWED : MODE_IGNORED); + mAppOpsManager.setUidMode( + OP_UNARCHIVAL_CONFIRMATION, + callingUid, + enableUnarchivalConfirmation ? MODE_ALLOWED : MODE_IGNORED); + }); + } + /** Checks if user is a profile of or same as listeningUser. * and the user is enabled. */ private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user, diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 09a91eda483a..c1b3673865dc 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_ABORTED; import static android.app.ActivityManager.START_CLASS_NOT_FOUND; import static android.app.ActivityManager.START_PERMISSION_DENIED; import static android.app.ActivityManager.START_SUCCESS; +import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; @@ -31,6 +32,7 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; +import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -66,8 +68,11 @@ import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.LayerDrawable; import android.os.Binder; import android.os.Bundle; @@ -269,11 +274,12 @@ public class PackageArchiver { Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName)); try { - // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options. requestUnarchive(packageName, callerPackageName, getOrCreateLauncherListener(userId, packageName), UserHandle.of(userId), - false /* showUnarchivalConfirmation= */); + getAppOpsManager().checkOp( + AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName) + == MODE_ALLOWED); } catch (Throwable t) { Slog.e(TAG, TextUtils.formatSimple( "Unexpected error occurred while unarchiving package %s: %s.", packageName, @@ -379,9 +385,8 @@ public class PackageArchiver { verifyNotSystemApp(ps.getFlags()); verifyInstalled(ps, userId); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); - verifyInstaller(responsibleInstallerPackage, userId); - ApplicationInfo installerInfo = snapshot.getApplicationInfo( - responsibleInstallerPackage, /* flags= */ 0, userId); + ApplicationInfo installerInfo = verifyInstaller( + snapshot, responsibleInstallerPackage, userId); verifyOptOutStatus(packageName, UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId()))); @@ -421,10 +426,10 @@ public class PackageArchiver { List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); for (int i = 0, size = mainActivities.size(); i < size; ++i) { var mainActivity = mainActivities.get(i); - Path iconPath = storeDrawable( - packageName, mainActivity.getIcon(), userId, i, iconSize); - Path monochromePath = storeDrawable( - packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize); + Path iconPath = storeAdaptiveDrawable( + packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize); + Path monochromePath = storeAdaptiveDrawable( + packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize); ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( mainActivity.getLabel().toString(), @@ -451,7 +456,8 @@ public class PackageArchiver { List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); - Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize); + Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize); + // i * 2 + 1 reserved for monochromeIcon ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( mainActivity.getLabel().toString(), @@ -495,8 +501,30 @@ public class PackageArchiver { return iconFile.toPath(); } - private void verifyInstaller(String installerPackageName, int userId) - throws PackageManager.NameNotFoundException { + /** + * Create an <a + * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive"> + * adaptive icon</a> from an icon. + * This is necessary so the icon can be displayed properly by different launchers. + */ + private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable, + @UserIdInt int userId, int index, int iconSize) throws IOException { + if (iconDrawable == null) { + return null; + } + + // see BaseIconFactory#createShapedIconBitmap + float inset = getExtraInsetFraction(); + inset = inset / (1 + 2 * inset); + Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), + new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset)); + + return storeDrawable(packageName, d, userId, index, iconSize); + } + + + private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName, + int userId) throws PackageManager.NameNotFoundException { if (TextUtils.isEmpty(installerPackageName)) { throw new PackageManager.NameNotFoundException("No installer found"); } @@ -505,6 +533,12 @@ public class PackageArchiver { && !verifySupportsUnarchival(installerPackageName, userId)) { throw new PackageManager.NameNotFoundException("Installer does not support unarchival"); } + ApplicationInfo appInfo = snapshot.getApplicationInfo( + installerPackageName, /* flags=*/ 0, userId); + if (appInfo == null) { + throw new PackageManager.NameNotFoundException("Failed to obtain Installer info"); + } + return appInfo; } /** @@ -570,7 +604,7 @@ public class PackageArchiver { } try { - verifyInstaller(getResponsibleInstallerPackage(ps), userId); + verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId); getLauncherActivityInfos(packageName, userId); } catch (PackageManager.NameNotFoundException e) { return false; @@ -762,7 +796,8 @@ public class PackageArchiver { * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple * launcher activities, only one of the icons is returned arbitrarily. */ - public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) { + public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user, + String callingPackageName) { Objects.requireNonNull(packageName); Objects.requireNonNull(user); @@ -785,7 +820,13 @@ public class PackageArchiver { // TODO(b/298452477) Handle monochrome icons. // In the rare case the archived app defined more than two launcher activities, we choose // the first one arbitrarily. - return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0))); + Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0)); + if (getAppOpsManager().checkOp( + AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName) + == MODE_ALLOWED) { + icon = includeCloudOverlay(icon); + } + return icon; } /** diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 7bf9fe7aa7e2..cfafe7cc00df 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -788,6 +788,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + if (Flags.recoverabilityDetection()) { + if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH + || params.rollbackImpactLevel + == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) { + if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { + throw new IllegalArgumentException( + "Can't set rollbackImpactLevel when rollback is not enabled"); + } + if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission"); + } + } else if (params.rollbackImpactLevel < 0) { + throw new IllegalArgumentException("rollbackImpactLevel can't be negative."); + } + } + boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0; if (isApex) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 117d03fd059b..1a0e8079996e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1294,6 +1294,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.autoRevokePermissionsMode = params.autoRevokePermissionsMode; info.installFlags = params.installFlags; info.rollbackLifetimeMillis = params.rollbackLifetimeMillis; + info.rollbackImpactLevel = params.rollbackImpactLevel; info.isMultiPackage = params.isMultiPackage; info.isStaged = params.isStaged; info.rollbackDataPolicy = params.rollbackDataPolicy; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c5b5a761497d..f09fa21792dd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4608,6 +4608,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService }); // Send UNSTOPPED broadcast if necessary if (wasStopped && Flags.stayStopped()) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "unstoppedBroadcast"); final PackageManagerInternal pmi = mInjector.getLocalService(PackageManagerInternal.class); final int [] userIds = resolveUserIds(userId); @@ -4627,6 +4628,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED, packageName, extras, userIds, null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } } @@ -6386,8 +6388,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override - public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) { - return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user); + public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user, + @NonNull String callingPackageName) { + return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user, + callingPackageName); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index ca00c84da724..88dc60c3f70f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -297,6 +297,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetHiddenSetting(true); case "unhide": return runSetHiddenSetting(false); + case "unstop": + return runSetStoppedState(false); case "suspend": return runSuspend(true, 0); case "suspend-quarantine": @@ -2662,6 +2664,26 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runSetStoppedState(boolean state) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + final int translatedUserId = + translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState"); + mInterface.setPackageStoppedState(pkg, state, userId); + getOutPrintWriter().println("Package " + pkg + " new stopped state: " + + mInterface.isPackageStoppedForUser(pkg, translatedUserId)); + return 0; + } + private int runSetDistractingRestriction() { final PrintWriter pw = getOutPrintWriter(); int userId = UserHandle.USER_SYSTEM; @@ -4934,6 +4956,8 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT"); pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); pw.println(""); + pw.println(" unstop [--user USER_ID] PACKAGE"); + pw.println(""); pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]"); pw.println(" Suspends the specified package(s) (as user)."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c94111c31ef4..a6598d602d01 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -712,19 +712,24 @@ public class UserManagerService extends IUserManager.Stub { boolean isAutoLockOnDeviceLockSelected = autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK; if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) { - int privateProfileUserId = getPrivateProfileUserId(); - if (privateProfileUserId != UserHandle.USER_NULL) { - Slog.i(LOG_TAG, "Auto-locking private space with user-id " - + privateProfileUserId); - setQuietModeEnabledAsync(privateProfileUserId, - /* enableQuietMode */true, /* target */ null, - mContext.getPackageName()); - } + autoLockPrivateSpace(); } } } @VisibleForTesting + void autoLockPrivateSpace() { + int privateProfileUserId = getPrivateProfileUserId(); + if (privateProfileUserId != UserHandle.USER_NULL) { + Slog.i(LOG_TAG, "Auto-locking private space with user-id " + + privateProfileUserId); + setQuietModeEnabledAsync(privateProfileUserId, + /* enableQuietMode */true, /* target */ null, + mContext.getPackageName()); + } + } + + @VisibleForTesting void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode, IntentSender target, @Nullable String callingPackage) { if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) { @@ -1036,9 +1041,18 @@ public class UserManagerService extends IUserManager.Stub { } } + if (isAutoLockingPrivateSpaceOnRestartsEnabled()) { + autoLockPrivateSpace(); + } + markEphemeralUsersForRemoval(); } + private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts(); + } + /** * This method retrieves the {@link UserManagerInternal} only for the purpose of * PackageManagerService construction. diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java index 3efac81d44e3..d138606369b9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java +++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java @@ -26,6 +26,7 @@ import android.util.ArrayMap; public final class PermissionAllowlist { @NonNull private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>(); + @NonNull private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist = new ArrayMap<>(); @@ -43,6 +44,19 @@ public final class PermissionAllowlist { mApexPrivilegedAppAllowlists = new ArrayMap<>(); @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist = + new ArrayMap<>(); + @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist = + new ArrayMap<>(); + @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist = + new ArrayMap<>(); + @NonNull + private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist = + new ArrayMap<>(); + + @NonNull public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() { return mOemAppAllowlist; } @@ -73,6 +87,26 @@ public final class PermissionAllowlist { return mApexPrivilegedAppAllowlists; } + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getSignatureAppAllowlist() { + return mSignatureAppAllowlist; + } + + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getVendorSignatureAppAllowlist() { + return mVendorSignatureAppAllowlist; + } + + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getProductSignatureAppAllowlist() { + return mProductSignatureAppAllowlist; + } + + @NonNull + public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtSignatureAppAllowlist() { + return mSystemExtSignatureAppAllowlist; + } + @Nullable public Boolean getOemAppAllowlistState(@NonNull String packageName, @NonNull String permissionName) { @@ -137,4 +171,44 @@ public final class PermissionAllowlist { } return permissions.get(permissionName); } + + @Nullable + public Boolean getSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } + + @Nullable + public Boolean getVendorSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mVendorSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } + + @Nullable + public Boolean getProductSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mProductSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } + + @Nullable + public Boolean getSystemExtSignatureAppAllowlistState(@NonNull String packageName, + @NonNull String permissionName) { + ArrayMap<String, Boolean> permissions = mSystemExtSignatureAppAllowlist.get(packageName); + if (permissions == null) { + return null; + } + return permissions.get(permissionName); + } } diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java index 5e8b4de3894e..7808c4ed50a4 100644 --- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java +++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.os.Environment; +import android.permission.flags.Flags; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; @@ -368,6 +369,7 @@ public class RoleServicePlatformHelperImpl implements RoleServicePlatformHelper dataOutputStream.writeUTF(profileOwner); dataOutputStream.writeInt(Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0)); + dataOutputStream.writeBoolean(Flags.walletRoleEnabled()); dataOutputStream.flush(); } catch (IOException e) { // Never happens for MessageDigestOutputStream and DataOutputStream. diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig index ed981e0aca74..2154a26acbaf 100644 --- a/services/core/java/com/android/server/policy/window_policy_flags.aconfig +++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig @@ -4,5 +4,5 @@ flag { name: "support_input_wakeup_delegate" namespace: "wear_frameworks" description: "Whether or not window policy allows injecting input wake-up delegate." - bug: "298055811" + bug: "319132073" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index a5b90f12a5cc..a580bb7c741a 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -215,7 +215,8 @@ class Rollback { /* packages */ new ArrayList<>(), /* isStaged */ isStaged, /* causePackages */ new ArrayList<>(), - /* committedSessionId */ -1); + /* committedSessionId */ -1, + /* rollbackImpactLevel */ PackageManager.ROLLBACK_USER_IMPACT_LOW); mUserId = userId; mInstallerPackageName = installerPackageName; mBackupDir = backupDir; @@ -394,7 +395,8 @@ class Rollback { */ @WorkerThread boolean enableForPackage(String packageName, long newVersion, long installedVersion, - boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy) { + boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy, + @PackageManager.RollbackImpactLevel int rollbackImpactLevel) { assertInWorkerThread(); try { RollbackStore.backupPackageCodePath(this, packageName, sourceDir); @@ -415,6 +417,10 @@ class Rollback { isApex, false /* isApkInApex */, new ArrayList<>(), rollbackDataPolicy); info.getPackages().add(packageRollbackInfo); + + if (info.getRollbackImpactLevel() < rollbackImpactLevel) { + info.setRollbackImpactLevel(rollbackImpactLevel); + } return true; } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 13f114138261..359678b15213 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -954,7 +954,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba ApplicationInfo appInfo = pkgInfo.applicationInfo; return rollback.enableForPackage(packageName, newPackage.getVersionCode(), pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir, - appInfo.splitSourceDirs, rollbackDataPolicy); + appInfo.splitSourceDirs, rollbackDataPolicy, session.rollbackImpactLevel); } @ExtThread diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 0af137faf5b4..14539d544bf9 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -193,16 +193,27 @@ class RollbackStore { json.put("isStaged", rollback.isStaged()); json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages())); json.put("committedSessionId", rollback.getCommittedSessionId()); + if (Flags.recoverabilityDetection()) { + json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel()); + } return json; } private static RollbackInfo rollbackInfoFromJson(JSONObject json) throws JSONException { - return new RollbackInfo( + RollbackInfo rollbackInfo = new RollbackInfo( json.getInt("rollbackId"), packageRollbackInfosFromJson(json.getJSONArray("packages")), json.getBoolean("isStaged"), versionedPackagesFromJson(json.getJSONArray("causePackages")), json.getInt("committedSessionId")); + + if (Flags.recoverabilityDetection()) { + // to make it backward compatible. + rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel", + PackageManager.ROLLBACK_USER_IMPACT_LOW)); + } + + return rollbackInfo; } /** diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 73fc8e9cfbc4..ac1b4df930eb 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -3860,6 +3860,23 @@ public final class TvInputManagerService extends SystemService { } @Override + public void onVideoFreezeUpdated(boolean isFrozen) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onVideoFreezeUpdated(" + isFrozen + ")"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onVideoFreezeUpdated(isFrozen, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onVideoFreezeUpdated", e); + } + } + } + + @Override public void onContentAllowed() { synchronized (mLock) { if (DEBUG) { diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 7ab075e2f3a7..9c60fbb6bb9a 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -1470,6 +1470,28 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override + public void notifyVideoFreezeUpdated(IBinder sessionToken, boolean isFrozen, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyVideoFreezeUpdated"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyVideoFreezeUpdated(isFrozen); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyVideoFreezeUpdated", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void notifyContentAllowed(IBinder sessionToken, int userId) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -1557,7 +1579,6 @@ public class TvInteractiveAppManagerService extends SystemService { } } - @Override public void notifyRecordingStarted(IBinder sessionToken, String recordingId, String requestId, int userId) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 1c90e30c256e..2049331e8efe 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -46,6 +46,7 @@ #include <com_android_input_flags.h> #include <input/Input.h> #include <input/PointerController.h> +#include <input/PrintTools.h> #include <input/SpriteController.h> #include <inputflinger/InputManager.h> #include <limits.h> @@ -230,10 +231,6 @@ inline static T max(const T& a, const T& b) { return a > b ? a : b; } -static inline const char* toString(bool value) { - return value ? "true" : "false"; -} - static SpriteIcon toSpriteIcon(PointerIcon pointerIcon) { // As a minor optimization, do not make a copy of the PointerIcon bitmap here. The loaded // PointerIcons are only cached by InputManagerService in java, so we can safely assume they @@ -288,7 +285,7 @@ public: void setSystemUiLightsOut(bool lightsOut); void setPointerDisplayId(int32_t displayId); void setPointerSpeed(int32_t speed); - void setMousePointerAccelerationEnabled(bool enabled); + void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled); void setTouchpadPointerSpeed(int32_t speed); void setTouchpadNaturalScrollingEnabled(bool enabled); void setTouchpadTapToClickEnabled(bool enabled); @@ -304,6 +301,7 @@ public: bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken); + void setPointerIconVisibility(int32_t displayId, bool visible); void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); @@ -400,8 +398,8 @@ private: // Pointer speed. int32_t pointerSpeed{0}; - // True if pointer acceleration is enabled for mice. - bool mousePointerAccelerationEnabled{true}; + // Displays on which its associated mice will have pointer acceleration disabled. + std::set<int32_t> displaysWithMousePointerAccelerationDisabled{}; // True if pointer gestures are enabled. bool pointerGesturesEnabled{true}; @@ -492,8 +490,8 @@ void NativeInputManager::dump(std::string& dump) { dump += StringPrintf(INDENT "System UI Lights Out: %s\n", toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); - dump += StringPrintf(INDENT "Mouse Pointer Acceleration: %s\n", - mLocked.mousePointerAccelerationEnabled ? "Enabled" : "Disabled"); + dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n", + dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str()); dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n", toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches)); @@ -676,11 +674,13 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon std::scoped_lock _l(mLock); outConfig->mousePointerSpeed = mLocked.pointerSpeed; - outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled; - outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed - * POINTER_SPEED_EXPONENT); + outConfig->displaysWithMousePointerAccelerationDisabled = + mLocked.displaysWithMousePointerAccelerationDisabled; + outConfig->pointerVelocityControlParameters.scale = + exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT); outConfig->pointerVelocityControlParameters.acceleration = - mLocked.mousePointerAccelerationEnabled + mLocked.displaysWithMousePointerAccelerationDisabled.count( + mLocked.pointerDisplayId) == 0 ? android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION : 1; outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled; @@ -1224,16 +1224,23 @@ void NativeInputManager::setPointerSpeed(int32_t speed) { InputReaderConfiguration::Change::POINTER_SPEED); } -void NativeInputManager::setMousePointerAccelerationEnabled(bool enabled) { +void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) { { // acquire lock std::scoped_lock _l(mLock); - if (mLocked.mousePointerAccelerationEnabled == enabled) { + const bool oldEnabled = + mLocked.displaysWithMousePointerAccelerationDisabled.count(displayId) == 0; + if (oldEnabled == enabled) { return; } - ALOGI("Setting mouse pointer acceleration to %s", toString(enabled)); - mLocked.mousePointerAccelerationEnabled = enabled; + ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled), + displayId); + if (enabled) { + mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId); + } else { + mLocked.displaysWithMousePointerAccelerationDisabled.emplace(displayId); + } } // release lock mInputManager->getReader().requestRefreshConfiguration( @@ -1397,6 +1404,13 @@ bool NativeInputManager::setPointerIcon( return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId); } +void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) { + if (!ENABLE_POINTER_CHOREOGRAPHER) { + return; + } + mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible); +} + TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( JNIEnv *env, jfloatArray matrixArr) { ATRACE_CALL(); @@ -2168,10 +2182,10 @@ static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed } static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject nativeImplObj, - jboolean enabled) { + jint displayId, jboolean enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setMousePointerAccelerationEnabled(enabled); + im->setMousePointerAccelerationEnabled(displayId, enabled); } static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) { @@ -2550,6 +2564,13 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico ibinderForJavaObject(env, inputTokenObj)); } +static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, jint displayId, + jboolean visible) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + + im->setPointerIconVisibility(displayId, visible); +} + static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -2791,7 +2812,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeTransferTouchFocus}, {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch}, {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed}, - {"setMousePointerAccelerationEnabled", "(Z)V", + {"setMousePointerAccelerationEnabled", "(IZ)V", (void*)nativeSetMousePointerAccelerationEnabled}, {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed}, {"setTouchpadNaturalScrollingEnabled", "(Z)V", @@ -2828,6 +2849,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetCustomPointerIcon}, {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z", (void*)nativeSetPointerIcon}, + {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility}, {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay}, {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged}, {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation}, diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 62d2d7ee848a..4c74878dab70 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -22,6 +22,7 @@ import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo import android.content.pm.SigningDetails import android.os.Build +import android.permission.flags.Flags import android.util.Slog import com.android.internal.os.RoSystemProperties import com.android.internal.pm.permission.CompatibilityPermissionInfo @@ -1197,15 +1198,80 @@ class AppIdPermissionPolicy : SchemePolicy() { newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!! .androidPackage!! .signingDetails - return sourceSigningDetails?.hasCommonSignerWithCapability( - packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION - ) == true || - packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || - platformSigningDetails.checkCapability( + val hasCommonSigner = + sourceSigningDetails?.hasCommonSignerWithCapability( packageSigningDetails, SigningDetails.CertCapabilities.PERMISSION - ) + ) == true || + packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || + platformSigningDetails.checkCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) + if (!Flags.signaturePermissionAllowlistEnabled()) { + return hasCommonSigner; + } + if (!hasCommonSigner) { + return false + } + // A platform signature permission also needs to be allowlisted on non-debuggable builds. + if (permission.packageName == PLATFORM_PACKAGE_NAME) { + val isRequestedByFactoryApp = + if (packageState.isSystem) { + // For updated system applications, a signature permission still needs to be + // allowlisted if it wasn't requested by the original application. + if (packageState.isUpdatedSystemApp) { + val disabledSystemPackage = + newState.externalState.disabledSystemPackageStates[ + packageState.packageName] + ?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions + } else { + true + } + } else { + false + } + if ( + !(isRequestedByFactoryApp || + getSignaturePermissionAllowlistState(packageState, permission.name) == true) + ) { + Slog.w( + LOG_TAG, + "Signature permission ${permission.name} for package" + + " ${packageState.packageName} (${packageState.path}) not in" + + " signature permission allowlist" + ) + if (!Build.isDebuggable()) { + return false + } + } + } + return true + } + + private fun MutateStateScope.getSignaturePermissionAllowlistState( + packageState: PackageState, + permissionName: String + ): Boolean? { + val permissionAllowlist = newState.externalState.permissionAllowlist + val packageName = packageState.packageName + return when { + packageState.isVendor || packageState.isOdm -> + permissionAllowlist.getVendorSignatureAppAllowlistState(packageName, permissionName) + packageState.isProduct -> + permissionAllowlist.getProductSignatureAppAllowlistState( + packageName, + permissionName + ) + packageState.isSystemExt -> + permissionAllowlist.getSystemExtSignatureAppAllowlistState( + packageName, + permissionName + ) + else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName) + } } private fun MutateStateScope.checkPrivilegedPermissionAllowlist( diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp index 4fcdbfc21f6c..d479e52f92d8 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp +++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp @@ -11,7 +11,6 @@ // 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 { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java index 74506076d82f..c99e7129853b 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java +++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java @@ -41,17 +41,26 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk"; private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk"; + // TODO: Move the silent installs to test-app using {@link + // BackgroundInstallControlServiceTest#installPackage(String, String)} and remove deviceConfig + // branch in BICS. + // b/310983905 @Test public void testGetMockBackgroundInstalledPackages() throws Exception { - installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1); + installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1); installPackage(TEST_DATA_DIR + MOCK_APK_FILE_2); assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull(); assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull(); - assertThat(getDevice().setProperty("debug.transparency.bg-install-apps", - MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue(); - runDeviceTest("testGetMockBackgroundInstalledPackages"); + assertThat( + getDevice() + .setProperty( + "debug.transparency.bg-install-apps", + MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)) + .isTrue(); + runDeviceTest( + "BackgroundInstallControlServiceTest", "testGetMockBackgroundInstalledPackages"); assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull(); assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull(); @@ -65,10 +74,10 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue(); } - private void runDeviceTest(String method) throws DeviceNotAvailableException { + private void runDeviceTest(String testName, String method) throws DeviceNotAvailableException { var options = new DeviceTestRunOptions(PACKAGE_NAME); - options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest"); + options.setTestClassName(PACKAGE_NAME + "." + testName); options.setTestMethodName(method); runDeviceTests(options); } -} +}
\ No newline at end of file diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml index 1fa1f84cd04e..cbe58a8fec70 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml @@ -24,4 +24,4 @@ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:label="APCT tests for background install control service" android:targetPackage="com.android.server.pm.test.app" /> -</manifest> +</manifest>
\ No newline at end of file diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java index b74e5619fd0c..b23f59106881 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java @@ -16,6 +16,10 @@ package com.android.server.pm.test.app; +import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; + +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -23,12 +27,15 @@ import android.content.pm.IBackgroundInstallControlService; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; import androidx.test.runner.AndroidJUnit4; +import com.android.compatibility.common.util.ShellIdentityUtils; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,31 +46,52 @@ import java.util.stream.Collectors; @RunWith(AndroidJUnit4.class) public class BackgroundInstallControlServiceTest { private static final String TAG = "BackgroundInstallControlServiceTest"; + private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3"; private IBackgroundInstallControlService mIBics; @Before public void setUp() { - mIBics = IBackgroundInstallControlService.Stub.asInterface( - ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); + mIBics = + IBackgroundInstallControlService.Stub.asInterface( + ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); assertThat(mIBics).isNotNull(); } + @After + public void tearDown() { + runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME); + } + @Test public void testGetMockBackgroundInstalledPackages() throws RemoteException { - ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages( - PackageManager.MATCH_ALL, - UserHandle.USER_ALL); + ParceledListSlice<PackageInfo> slice = + ShellIdentityUtils.invokeMethodWithShellPermissions( + mIBics, + (bics) -> { + try { + return bics.getBackgroundInstalledPackages( + PackageManager.MATCH_ALL, Process.myUserHandle() + .getIdentifier()); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }, + GET_BACKGROUND_INSTALLED_PACKAGES); assertThat(slice).isNotNull(); var packageList = slice.getList(); assertThat(packageList).isNotNull(); assertThat(packageList).hasSize(2); - var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1", - "com.android.servicestests.apps.bicmockapp2"); - var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName) - .collect(Collectors.toSet()); + var expectedPackageNames = + Set.of( + "com.android.servicestests.apps.bicmockapp1", + "com.android.servicestests.apps.bicmockapp2"); + var actualPackageNames = + packageList.stream() + .map((packageInfo) -> packageInfo.packageName) + .collect(Collectors.toSet()); assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames); } -} +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java index d82e6abeb502..23e3e2560524 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 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. @@ -26,18 +26,13 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; -import org.junit.runner.RunWith; import java.io.File; import java.util.List; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class AdditionalSubtypeUtilsTest { +public final class AdditionalSubtypeUtilsTest { @Test public void testSaveAndLoad() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java index 6eedeea8c333..b7223d6615b9 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 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. @@ -19,17 +19,11 @@ package com.android.server.inputmethod; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Test; -import org.junit.runner.RunWith; import java.util.Arrays; import java.util.List; -@SmallTest -@RunWith(AndroidJUnit4.class) public final class HardwareKeyboardShortcutControllerTest { @Test diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java index 7cbfc52c5cce..570132f5a7d8 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java @@ -28,6 +28,7 @@ import android.content.pm.ResolveInfo; import android.util.ArrayMap; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -123,10 +124,12 @@ public class InputMethodManagerServiceRestrictImeAmountTest extends private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList, List<String> enabledComponents) { + final ArrayMap<String, List<InputMethodSubtype>> emptyAdditionalSubtypeMap = + new ArrayMap<>(); final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList, - enabledComponents, mContext, resolveInfoList); + InputMethodManagerService.filterInputMethodServices(emptyAdditionalSubtypeMap, methodMap, + methodList, enabledComponents, mContext, resolveInfoList); return methodList; } diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index 0884b784ac73..fbe384a62658 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2024 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. @@ -28,22 +28,16 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class InputMethodSubtypeSwitchingControllerTest { +public final class InputMethodSubtypeSwitchingControllerTest { private static final String DUMMY_PACKAGE_NAME = "dummy package name"; private static final String DUMMY_IME_LABEL = "dummy ime label"; private static final String DUMMY_SETTING_ACTIVITY_NAME = ""; diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index 9688ef6cc83b..ac485be5b5a1 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2024 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. @@ -41,15 +41,12 @@ import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.inputmethod.StartInputFlags; import com.google.common.truth.Truth; import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collections; @@ -57,9 +54,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class InputMethodUtilsTest { +public final class InputMethodUtilsTest { private static final boolean IS_AUX = true; private static final boolean IS_DEFAULT = true; private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true; diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java index 01f8129c2cd7..d0b46f58d626 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2024 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. @@ -22,18 +22,12 @@ import static org.junit.Assert.assertEquals; import android.os.LocaleList; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Locale; -@SmallTest -@RunWith(AndroidJUnit4.class) -public class LocaleUtilsTest { +public final class LocaleUtilsTest { private static final LocaleUtils.LocaleExtractor<Locale> sIdentityMapper = source -> source; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index 978044045ab3..a185ad99a8f3 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -419,7 +419,7 @@ public class PackageUserStateTest { "installerTitle"); packageUserState.setArchiveState(archiveState); assertEquals(archiveState, packageUserState.getArchiveState()); - assertTrue(archiveState.getArchiveTimeMillis() > currentTimeMillis); + assertTrue(archiveState.getArchiveTimeMillis() >= currentTimeMillis); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 284e491f8081..93a2eefba5b1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -31,13 +31,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; +import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS; -import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE; -import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINES; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; @@ -76,6 +80,7 @@ import android.os.PowerManager; import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.EmptyArray; +import android.util.SparseArray; import com.android.server.AppSchedulingModuleThread; import com.android.server.DeviceIdleInternal; @@ -96,6 +101,7 @@ import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneOffset; import java.util.ArrayList; @@ -182,7 +188,12 @@ public class FlexibilityControllerTest { mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); - setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80"); + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + "500=50|60|70|80" + + ",400=50|60|70|80" + + ",300=50|60|70|80" + + ",200=50|60|70|80" + + ",100=50|60|70|80"); setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L); setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS); waitForQuietModuleThread(); @@ -200,6 +211,11 @@ public class FlexibilityControllerTest { } } + private void advanceElapsedClock(long incrementMs) { + JobSchedulerService.sElapsedRealtimeClock = Clock.offset( + sElapsedRealtimeClock, Duration.ofMillis(incrementMs)); + } + private void setDeviceConfigInt(String key, int val) { mDeviceConfigPropertiesBuilder.setInt(key, val); updateDeviceConfig(key); @@ -250,9 +266,12 @@ public class FlexibilityControllerTest { */ @Test public void testDefaultVariableValues() { - assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS), - mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length - ); + SparseArray<int[]> defaultPercentsToDrop = + FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; + for (int i = 0; i < defaultPercentsToDrop.size(); ++i) { + assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS), + defaultPercentsToDrop.valueAt(i).length); + } } @Test @@ -379,10 +398,13 @@ public class FlexibilityControllerTest { @Test public void testOnConstantsUpdated_FallbackDeadline() { JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0)); - assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L)); - setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L); - assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L)); + final long nowElapsed = sElapsedRealtimeClock.millis(); + assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.get(JobInfo.PRIORITY_DEFAULT), + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L)); + setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 123L); + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, + "500=500,400=400,300=300,200=200,100=100"); + assertEquals(300L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L)); } @Test @@ -392,10 +414,32 @@ public class FlexibilityControllerTest { JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); - setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40"); + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + "500=1|2|3|4" + + ",400=5|6|7|8" + + ",300=10|20|30|40" + + ",200=50|51|52|53" + + ",100=54|55|56|57"); + assertArrayEquals( + mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .get(JobInfo.PRIORITY_MAX), + new int[]{1, 2, 3, 4}); + assertArrayEquals( + mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .get(JobInfo.PRIORITY_HIGH), + new int[]{5, 6, 7, 8}); + assertArrayEquals( + mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .get(JobInfo.PRIORITY_DEFAULT), + new int[]{10, 20, 30, 40}); assertArrayEquals( - mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, - new int[] {10, 20, 30, 40}); + mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .get(JobInfo.PRIORITY_LOW), + new int[]{50, 51, 52, 53}); + assertArrayEquals( + mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS + .get(JobInfo.PRIORITY_MIN), + new int[]{54, 55, 56, 57}); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); js.setNumDroppedFlexibleConstraints(1); @@ -408,25 +452,64 @@ public class FlexibilityControllerTest { @Test public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() { - JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); - JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb); - js.enqueueTime = JobSchedulerService.sElapsedRealtimeClock.millis(); - assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, - mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); - setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40"); - assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, - mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); - setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40"); - assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, - mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); - setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40"); - assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, - mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); + // No priority mapping + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, "10,20,30,40"); + final SparseArray<int[]> defaultPercentsToDrop = + FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; + final SparseArray<int[]> percentsToDrop = + mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; + for (int i = 0; i < defaultPercentsToDrop.size(); ++i) { + assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i)); + } + + // Invalid priority-percentList string + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + "500=10,20a,030,40" + + ",400=20|40|60|80" + + ",300=25|50|75|80" + + ",200=40|50|60|80" + + ",100=20|40|60|80"); + for (int i = 0; i < defaultPercentsToDrop.size(); ++i) { + assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i)); + } + + // Invalid percentList strings + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + "500=10|20a|030|40" // Letters + + ",400=10|40" // Not enough + + ",300=.|50|_|80" // Characters + + ",200=50|40|10|40" // Out of order + + ",100=30|60|90|101"); // Over 100 + for (int i = 0; i < defaultPercentsToDrop.size(); ++i) { + assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i)); + } + + // Only partially invalid + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + "500=10|20a|030|40" // Letters + + ",400=10|40" // Not enough + + ",300=.|50|_|80" // Characters + + ",200=10|20|30|40" // Valid + + ",100=20|40|60|80"); // Valid + assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_MAX), + percentsToDrop.get(JobInfo.PRIORITY_MAX)); + assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_HIGH), + percentsToDrop.get(JobInfo.PRIORITY_HIGH)); + assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_DEFAULT), + percentsToDrop.get(JobInfo.PRIORITY_DEFAULT)); + assertArrayEquals(new int[]{10, 20, 30, 40}, percentsToDrop.get(JobInfo.PRIORITY_LOW)); + assertArrayEquals(new int[]{20, 40, 60, 80}, percentsToDrop.get(JobInfo.PRIORITY_MIN)); } @Test public void testGetNextConstraintDropTimeElapsedLocked() { setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS); + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, + "500=" + HOUR_IN_MILLIS + + ",400=" + 25 * HOUR_IN_MILLIS + + ",300=" + 50 * HOUR_IN_MILLIS + + ",200=" + 100 * HOUR_IN_MILLIS + + ",100=" + 200 * HOUR_IN_MILLIS); long nextTimeToDropNumConstraints; @@ -459,34 +542,34 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2, + assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10, + assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10, + assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints); // no delay, no deadline - jb = createJob(0); + jb = createJob(0).setPriority(JobInfo.PRIORITY_LOW); js = createJobStatus("time", jb); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints); // delay, deadline jb = createJob(0) @@ -514,13 +597,13 @@ public class FlexibilityControllerTest { @Test public void testCurPercent() { long deadline = 100 * MINUTE_IN_MILLIS; - long nowElapsed; + long nowElapsed = FROZEN_TIME; JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline); JobStatus js = createJobStatus("time", jb); assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); assertEquals(deadline + FROZEN_TIME, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME)); nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); @@ -547,7 +630,8 @@ public class FlexibilityControllerTest { assertEquals(FROZEN_TIME + delay, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); assertEquals(deadline + FROZEN_TIME, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, + FROZEN_TIME + delay)); nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = @@ -670,34 +754,63 @@ public class FlexibilityControllerTest { @Test public void testGetLifeCycleEndElapsedLocked_Prefetch() { + final long nowElapsed = sElapsedRealtimeClock.millis(); + // prefetch no estimate JobInfo.Builder jb = createJob(0).setPrefetch(true); JobStatus js = createJobStatus("time", jb); doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); - assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + assertEquals(Long.MAX_VALUE, + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); + // prefetch with estimate jb = createJob(0).setPrefetch(true); js = createJobStatus("time", jb); doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); - assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + assertEquals(1000L, + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); } @Test public void testGetLifeCycleEndElapsedLocked_NonPrefetch() { + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, + "500=" + HOUR_IN_MILLIS + + ",400=" + 2 * HOUR_IN_MILLIS + + ",300=" + 3 * HOUR_IN_MILLIS + + ",200=" + 4 * HOUR_IN_MILLIS + + ",100=" + 5 * HOUR_IN_MILLIS); + + final long nowElapsed = sElapsedRealtimeClock.millis(); + // deadline JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); JobStatus js = createJobStatus("time", jb); assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); + // no deadline - jb = createJob(0); - js = createJobStatus("time", jb); - assertEquals(FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L)); + assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)), + nowElapsed, 100L)); + assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)), + nowElapsed, 100L)); + assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)), + nowElapsed, 100L)); + assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)), + nowElapsed, 100L)); } @Test public void testGetLifeCycleEndElapsedLocked_Rescheduled() { + final long nowElapsed = sElapsedRealtimeClock.millis(); + JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); JobStatus js = createJobStatus("time", jb); js = new JobStatus( @@ -705,20 +818,91 @@ public class FlexibilityControllerTest { 0, FROZEN_TIME, FROZEN_TIME); assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); js = new JobStatus( js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1, 0, FROZEN_TIME, FROZEN_TIME); assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); js = new JobStatus( js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10, 0, FROZEN_TIME, FROZEN_TIME); assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); + } + + @Test + public void testGetLifeCycleEndElapsedLocked_ScoreAddition() { + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, + "500=" + HOUR_IN_MILLIS + + ",400=" + HOUR_IN_MILLIS + + ",300=" + HOUR_IN_MILLIS + + ",200=" + HOUR_IN_MILLIS + + ",100=" + HOUR_IN_MILLIS); + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES, + "500=5,400=4,300=3,200=2,100=1"); + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS, + "500=" + 5 * MINUTE_IN_MILLIS + + ",400=" + 4 * MINUTE_IN_MILLIS + + ",300=" + 3 * MINUTE_IN_MILLIS + + ",200=" + 2 * MINUTE_IN_MILLIS + + ",100=" + 1 * MINUTE_IN_MILLIS); + + final long nowElapsed = sElapsedRealtimeClock.millis(); + + JobStatus jsMax = createJobStatus("testScoreCalculation", + createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)); + JobStatus jsHigh = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jsDefault = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)); + JobStatus jsLow = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_LOW)); + JobStatus jsMin = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_MIN)); + // Make score = 15 + mFlexibilityController.prepareForExecutionLocked(jsMax); + mFlexibilityController.prepareForExecutionLocked(jsHigh); + mFlexibilityController.prepareForExecutionLocked(jsDefault); + mFlexibilityController.prepareForExecutionLocked(jsLow); + mFlexibilityController.prepareForExecutionLocked(jsMin); + + // deadline + JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); + JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb); + assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0)); + + final long earliestMs = 123L; + // no deadline + assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", + createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)), + nowElapsed, earliestMs)); + assertEquals(earliestMs + HOUR_IN_MILLIS + 4 * 15 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH)), + nowElapsed, earliestMs)); + assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)), + nowElapsed, earliestMs)); + assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", + createJob(0).setPriority(JobInfo.PRIORITY_LOW)), + nowElapsed, earliestMs)); + assertEquals(earliestMs + HOUR_IN_MILLIS + 1 * 15 * MINUTE_IN_MILLIS, + mFlexibilityController.getLifeCycleEndElapsedLocked( + createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", + createJob(0).setPriority(JobInfo.PRIORITY_MIN)), + nowElapsed, earliestMs)); } @Test @@ -734,6 +918,14 @@ public class FlexibilityControllerTest { @Test public void testFlexibilityTracker() { + setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100 * HOUR_IN_MILLIS); + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, + "500=" + 100 * HOUR_IN_MILLIS + + ",400=" + 100 * HOUR_IN_MILLIS + + ",300=" + 100 * HOUR_IN_MILLIS + + ",200=" + 100 * HOUR_IN_MILLIS + + ",100=" + 100 * HOUR_IN_MILLIS); + FlexibilityController.FlexibilityTracker flexTracker = mFlexibilityController.new FlexibilityTracker(4); // Plus one for jobs with 0 required constraint. @@ -805,8 +997,7 @@ public class FlexibilityControllerTest { assertEquals(1, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); - final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2) - + HOUR_IN_MILLIS); + final long nowElapsed = 51 * HOUR_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); @@ -1220,66 +1411,161 @@ public class FlexibilityControllerTest { @Test public void testCalculateNumDroppedConstraints() { - JobInfo.Builder jb = createJob(22); - JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb); - long nowElapsed = FROZEN_TIME; - - mFlexibilityController.mFlexibilityTracker.add(js); - - assertEquals(3, js.getNumRequiredFlexibleConstraints()); - assertEquals(0, js.getNumDroppedFlexibleConstraints()); - assertEquals(1, mFlexibilityController + setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, + "500=" + HOUR_IN_MILLIS + + ",400=" + 5 * HOUR_IN_MILLIS + + ",300=" + 8 * HOUR_IN_MILLIS + + ",200=" + 10 * HOUR_IN_MILLIS + + ",100=" + 20 * HOUR_IN_MILLIS); + setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, + "500=20|40|60|80" + + ",400=20|40|60|80" + + ",300=25|50|75|80" + + ",200=40|50|60|80" + + ",100=20|40|60|80"); + + JobStatus jsHigh = createJobStatus("testCalculateNumDroppedConstraints", + createJob(24).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jsDefault = createJobStatus("testCalculateNumDroppedConstraints", + createJob(23).setPriority(JobInfo.PRIORITY_DEFAULT)); + JobStatus jsLow = createJobStatus("testCalculateNumDroppedConstraints", + createJob(22).setPriority(JobInfo.PRIORITY_LOW)); + JobStatus jsMin = createJobStatus("testCalculateNumDroppedConstraints", + createJob(21).setPriority(JobInfo.PRIORITY_MIN)); + final long startElapsed = FROZEN_TIME; + long nowElapsed = startElapsed; + + mFlexibilityController.mFlexibilityTracker.add(jsHigh); + mFlexibilityController.mFlexibilityTracker.add(jsDefault); + mFlexibilityController.mFlexibilityTracker.add(jsLow); + mFlexibilityController.mFlexibilityTracker.add(jsMin); + + assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsLow.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsLow.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsMin.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsMin.getNumDroppedFlexibleConstraints()); + assertEquals(4, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); - nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5; + nowElapsed = startElapsed + HOUR_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); mFlexibilityController.mFlexibilityTracker - .setNumDroppedFlexibleConstraints(js, 1); - - assertEquals(2, js.getNumRequiredFlexibleConstraints()); - assertEquals(1, js.getNumDroppedFlexibleConstraints()); - assertEquals(1, mFlexibilityController - .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); - - mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); - - assertEquals(2, js.getNumRequiredFlexibleConstraints()); - assertEquals(1, js.getNumDroppedFlexibleConstraints()); + .calculateNumDroppedConstraints(jsHigh, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsDefault, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsLow, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsMin, nowElapsed); + + assertEquals(2, jsHigh.getNumRequiredFlexibleConstraints()); + assertEquals(1, jsHigh.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsLow.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsLow.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsMin.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsMin.getNumDroppedFlexibleConstraints()); + assertEquals(3, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); - nowElapsed = FROZEN_TIME; + nowElapsed = startElapsed; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); - - assertEquals(3, js.getNumRequiredFlexibleConstraints()); - assertEquals(0, js.getNumDroppedFlexibleConstraints()); - assertEquals(1, mFlexibilityController + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsHigh, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsDefault, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsLow, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsMin, nowElapsed); + + assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsLow.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsLow.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsMin.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsMin.getNumDroppedFlexibleConstraints()); + assertEquals(4, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); + assertEquals(0, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); + assertEquals(0, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size()); + assertEquals(0, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size()); - nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9; + nowElapsed = startElapsed + 3 * HOUR_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); - - assertEquals(0, js.getNumRequiredFlexibleConstraints()); - assertEquals(3, js.getNumDroppedFlexibleConstraints()); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsHigh, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsDefault, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsLow, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsMin, nowElapsed); + + assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints()); + assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints()); + assertEquals(2, jsDefault.getNumRequiredFlexibleConstraints()); + assertEquals(1, jsDefault.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsLow.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsLow.getNumDroppedFlexibleConstraints()); + assertEquals(3, jsMin.getNumRequiredFlexibleConstraints()); + assertEquals(0, jsMin.getNumDroppedFlexibleConstraints()); + assertEquals(2, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); + assertEquals(1, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); + assertEquals(0, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size()); + assertEquals(1, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size()); - nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6; + nowElapsed = startElapsed + 4 * HOUR_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); - - assertEquals(1, js.getNumRequiredFlexibleConstraints()); - assertEquals(2, js.getNumDroppedFlexibleConstraints()); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsHigh, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsDefault, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsLow, nowElapsed); + mFlexibilityController.mFlexibilityTracker + .calculateNumDroppedConstraints(jsMin, nowElapsed); + + assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints()); + assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints()); + assertEquals(1, jsDefault.getNumRequiredFlexibleConstraints()); + assertEquals(2, jsDefault.getNumDroppedFlexibleConstraints()); + assertEquals(2, jsLow.getNumRequiredFlexibleConstraints()); + assertEquals(1, jsLow.getNumDroppedFlexibleConstraints()); + assertEquals(2, jsMin.getNumRequiredFlexibleConstraints()); + assertEquals(1, jsMin.getNumDroppedFlexibleConstraints()); + assertEquals(0, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); + assertEquals(2, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size()); + assertEquals(1, mFlexibilityController + .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size()); } @Test @@ -1308,7 +1594,7 @@ public class FlexibilityControllerTest { .get(js.getSourceUserId(), js.getSourcePackageName())); assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); assertEquals(1150L, - mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L)); + mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 150L)); assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME)); assertEquals(650L, mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js)); @@ -1317,6 +1603,80 @@ public class FlexibilityControllerTest { .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size()); } + @Test + public void testScoreCalculation() { + JobStatus jsMax = createJobStatus("testScoreCalculation", + createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)); + JobStatus jsHigh = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jsDefault = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)); + JobStatus jsLow = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_LOW)); + JobStatus jsMin = createJobStatus("testScoreCalculation", + createJob(0).setPriority(JobInfo.PRIORITY_MIN)); + + long nowElapsed = sElapsedRealtimeClock.millis(); + assertEquals(0, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + mFlexibilityController.prepareForExecutionLocked(jsMax); + assertEquals(5, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + advanceElapsedClock(30 * MINUTE_IN_MILLIS); + nowElapsed += 30 * MINUTE_IN_MILLIS; + mFlexibilityController.prepareForExecutionLocked(jsMax); + assertEquals(10, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + advanceElapsedClock(31 * MINUTE_IN_MILLIS); + nowElapsed += 31 * MINUTE_IN_MILLIS; + mFlexibilityController.prepareForExecutionLocked(jsHigh); + assertEquals(14, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + advanceElapsedClock(2 * HOUR_IN_MILLIS); + nowElapsed += 2 * HOUR_IN_MILLIS; + mFlexibilityController.prepareForExecutionLocked(jsDefault); + assertEquals(17, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + advanceElapsedClock(3 * HOUR_IN_MILLIS); + nowElapsed += 3 * HOUR_IN_MILLIS; + mFlexibilityController.prepareForExecutionLocked(jsLow); + assertEquals(19, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + advanceElapsedClock(3 * HOUR_IN_MILLIS); + nowElapsed += 3 * HOUR_IN_MILLIS; + mFlexibilityController.prepareForExecutionLocked(jsMin); + assertEquals(20, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + advanceElapsedClock(3 * HOUR_IN_MILLIS); + nowElapsed += 3 * HOUR_IN_MILLIS; + mFlexibilityController.prepareForExecutionLocked(jsMax); + assertEquals(25, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + mFlexibilityController.unprepareFromExecutionLocked(jsMax); + assertEquals(20, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + // 24 hours haven't passed yet. The jobs in the first hour bucket should still be included. + advanceElapsedClock(12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1); + nowElapsed += 12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1; + assertEquals(20, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + // Passed the 24 hour mark. The jobs in the first hour bucket should no longer be included. + advanceElapsedClock(2); + nowElapsed += 2; + assertEquals(10, + mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed)); + + } + /** * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time * the estimated launch time was updated and the last time the app was opened. diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index ec7e35982311..e989d7b060be 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -552,20 +552,22 @@ public class PackageArchiverTest { when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( null); - assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull(); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT, + CALLER_PACKAGE)).isNull(); } @Test public void getArchivedAppIcon_notArchived() { - assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull(); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT, + CALLER_PACKAGE)).isNull(); } @Test public void getArchivedAppIcon_success() { mUserState.setArchiveState(createArchiveState()).setInstalled(false); - assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo( - mIcon); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT, + CALLER_PACKAGE)).isEqualTo(mIcon); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index e6298eeccafb..5bec903e6414 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -569,6 +569,23 @@ public final class UserManagerServiceTest { } @Test + public void testAutoLockPrivateProfile() { + UserManagerService mSpiedUms = spy(mUms); + UserInfo privateProfileUser = + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync( + eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(), + any()); + + mSpiedUms.autoLockPrivateSpace(); + + Mockito.verify(mSpiedUms).setQuietModeEnabledAsync( + eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), + any(), any()); + } + + @Test public void testAutoLockOnDeviceLockForPrivateProfile() { mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java index 9c8276aac4dd..84c0ab38ca48 100644 --- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java @@ -15,6 +15,7 @@ */ package com.android.server.audio; +import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE; import static android.media.AudioManager.GET_DEVICES_OUTPUTS; import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; @@ -22,6 +23,7 @@ import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_T import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; +import static android.os.Process.myPid; import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_LARGE; import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_MEDIUM; @@ -37,6 +39,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.media.AudioAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; @@ -64,13 +67,16 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; -import java.util.Random; @RunWith(AndroidJUnit4.class) @Presubmit public class LoudnessCodecHelperTest { private static final String TAG = "LoudnessCodecHelperTest"; + private static final int TEST_USAGE = AudioAttributes.USAGE_MEDIA; + + private static final int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_MUSIC; + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -83,94 +89,84 @@ public class LoudnessCodecHelperTest { private final int mInitialApcPiid = 1; + private int mSessionId; + @Before public void setUp() throws Exception { mLoudnessHelper = new LoudnessCodecHelper(mAudioService); + mSessionId = 1; when(mAudioService.getActivePlaybackConfigurations()).thenReturn( - getApcListForPiids(mInitialApcPiid)); + getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 0)); when(mDispatcher.asBinder()).thenReturn(Mockito.mock(IBinder.class)); } @Test - public void registerDispatcher_sendsInitialUpdateOnStart() throws Exception { + public void registerDispatcher_sendsUpdateOnAddCodec() throws Exception { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); + mLoudnessHelper.startLoudnessCodecUpdates(mSessionId); + mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, + getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); - verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); + verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); } @Test - public void unregisterDispatcher_noInitialUpdateOnStart() throws Exception { + public void unregisterDispatcher_noUpdateOnAdd() throws Exception { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*isDownmixing=*/false, CODEC_METADATA_TYPE_MPEG_D))); + mLoudnessHelper.startLoudnessCodecUpdates(mSessionId); + mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, + getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); - verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); } @Test - public void addCodecInfo_sendsInitialUpdateAfterStart() throws Exception { + public void addCodecInfoForDifferentId_noUpdateSent() throws Exception { + final int newSessionId = mSessionId + 1; mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); - mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222, + mLoudnessHelper.startLoudnessCodecUpdates(mSessionId); + mLoudnessHelper.addLoudnessCodecInfo(newSessionId, /*mediaCodecHash=*/222, getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); - verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); } @Test - public void addCodecInfoForUnstartedPiid_noUpdateSent() throws Exception { - final int newPiid = 2; + public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_4))); - mLoudnessHelper.addLoudnessCodecInfo(newPiid, /*mediaCodecHash=*/222, + mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); - verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), - any()); - } + mLoudnessHelper.updateCodecParameters( + getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1)); - @Test - public void updateCodecParameters_updatesOnlyStartedPiids() throws Exception { - final int newPiid = 2; - mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); - //does not trigger dispatch since active apc list does not contain newPiid - mLoudnessHelper.startLoudnessCodecUpdates(newPiid, - List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D))); - verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + // no dispatch since mSessionId was not started + verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); - - // triggers dispatch for new active apc with newPiid - mLoudnessHelper.updateCodecParameters(getApcListForPiids(newPiid)); - verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(newPiid), any()); } @Test - public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception { + public void updateCodecParameters_dispatchUpdates() throws Exception { + final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4); mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222, - getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); - mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); + mLoudnessHelper.startLoudnessCodecUpdates(mSessionId); + mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info); - // no dispatch since mInitialApcPiid was not started - verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + mLoudnessHelper.updateCodecParameters( + getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1)); + + // second dispatch since player configurations were updated + verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); } @@ -180,13 +176,15 @@ public class LoudnessCodecHelperTest { CODEC_METADATA_TYPE_MPEG_4); mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info)); - mLoudnessHelper.removeLoudnessCodecInfo(mInitialApcPiid, info); + mLoudnessHelper.startLoudnessCodecUpdates(mSessionId); + mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info); + mLoudnessHelper.removeLoudnessCodecInfo(mSessionId, info); - mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); + mLoudnessHelper.updateCodecParameters( + getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1)); // no second dispatch since codec info was removed for updates - verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); } @@ -196,13 +194,14 @@ public class LoudnessCodecHelperTest { CODEC_METADATA_TYPE_MPEG_4); mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info)); - mLoudnessHelper.stopLoudnessCodecUpdates(mInitialApcPiid); + mLoudnessHelper.startLoudnessCodecUpdates(mSessionId); + mLoudnessHelper.stopLoudnessCodecUpdates(mSessionId); - mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); + mLoudnessHelper.updateCodecParameters( + getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1)); // no second dispatch since piid was removed for updates - verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId), any()); } @@ -308,23 +307,28 @@ public class LoudnessCodecHelperTest { assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); } - private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) { + private List<AudioPlaybackConfiguration> getApcListForApcWithPiidSid(int piid, int sessionId, + int devIdx) { final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>(); AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS); - assumeTrue(devicesStatic.length > 0); - int index = new Random().nextInt(devicesStatic.length); - Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + index); - int deviceId = devicesStatic[index].getId(); - - for (int piid : piids) { - PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class); - AudioPlaybackConfiguration apc = - new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/1); - apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId); - - apcList.add(apc); - } + assumeTrue(devIdx < devicesStatic.length); + Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + devIdx); + int deviceId = devicesStatic[devIdx].getId(); + + PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class); + AudioPlaybackConfiguration apc = + new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/myPid()); + apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId); + apc.handleSessionIdEvent(sessionId); + apc.handleAudioAttributesEvent(new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE) + .build()); + + apcList.add(apc); + return apcList; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index a8eace05de97..c7300bbb50a7 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -20,6 +20,7 @@ import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; @@ -33,11 +34,15 @@ import android.content.Intent; import android.hardware.biometrics.AuthenticateOptions; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricContextListener.FoldState; +import android.hardware.biometrics.common.DisplayState; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.common.OperationReason; import android.hardware.display.DisplayManagerGlobal; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.TestableContext; import android.view.Display; import android.view.DisplayInfo; @@ -72,6 +77,9 @@ public class BiometricContextProviderTest { @Rule public TestableContext mContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getContext()); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Mock private IStatusBarService mStatusBarService; @@ -395,6 +403,37 @@ public class BiometricContextProviderTest { } } + @Test + public void testSubscribe_thenStartHal() throws RemoteException { + Consumer<OperationContext> updateConsumer = mock(Consumer.class); + Consumer<OperationContext> startHalConsumer = mock(Consumer.class); + AuthenticateOptions options = new FingerprintAuthenticateOptions.Builder().build(); + OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */); + + assertThat(context.getDisplayState()).isEqualTo(DisplayState.UNKNOWN); + assertThat(context.getFoldState()).isEqualTo(IBiometricContextListener.FoldState.UNKNOWN); + + mListener.onDisplayStateChanged(DisplayState.LOCKSCREEN); + mListener.onFoldChanged(FoldState.FULLY_CLOSED); + mProvider.subscribe(context, startHalConsumer, updateConsumer, options); + + assertThat(context.getDisplayState()).isEqualTo(DisplayState.LOCKSCREEN); + assertThat(context.getFoldState()).isEqualTo(FoldState.FULLY_CLOSED); + verify(updateConsumer, never()).accept(context.toAidlContext()); + verify(startHalConsumer).accept(context.toAidlContext(options)); + } + + @Test + public void testSubscribe_withInvalidOptions() { + Consumer<OperationContext> updateConsumer = mock(Consumer.class); + Consumer<OperationContext> startHalConsumer = mock(Consumer.class); + AuthenticateOptions options = mock(AuthenticateOptions.class); + OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */); + + assertThrows(IllegalStateException.class, () -> mProvider.subscribe( + context, startHalConsumer, updateConsumer, options)); + } + private static byte reason(int type) { if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) { return OperationReason.BIOMETRIC_PROMPT; diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java index daf18edaf2de..8656f60afc1e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java @@ -9,13 +9,16 @@ * * 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. + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.Ã¥ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; +import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -27,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -97,7 +101,6 @@ public final class BackgroundInstallControlServiceTest { private Looper mLooper; private File mFile; - @Mock private Context mContext; @Mock @@ -108,8 +111,10 @@ public final class BackgroundInstallControlServiceTest { private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private PermissionManagerServiceInternal mPermissionManager; + @Captor private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor; + @Captor private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor; @@ -119,11 +124,12 @@ public final class BackgroundInstallControlServiceTest { mTestLooper = new TestLooper(); mLooper = mTestLooper.getLooper(); - mFile = new File( - InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), - "test"); - mBackgroundInstallControlService = new BackgroundInstallControlService( - new MockInjector(mContext)); + mFile = + new File( + InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), + "test"); + mBackgroundInstallControlService = + new BackgroundInstallControlService(new MockInjector(mContext)); verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture()); mUsageEventListener = mUsageEventListenerCaptor.getValue(); @@ -143,8 +149,7 @@ public final class BackgroundInstallControlServiceTest { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); mBackgroundInstallControlService.initBackgroundInstalledPackages(); assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - assertEquals(0, - mBackgroundInstallControlService.getBackgroundInstalledPackages().size()); + assertEquals(0, mBackgroundInstallControlService.getBackgroundInstalledPackages().size()); } @Test @@ -161,12 +166,9 @@ public final class BackgroundInstallControlServiceTest { // Write test data to the file on the disk. try { ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); - long token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); - protoOutputStream.write( - BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); + long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); protoOutputStream.end(token); protoOutputStream.flush(); atomicFile.finishWrite(fileOutputStream); @@ -198,20 +200,14 @@ public final class BackgroundInstallControlServiceTest { try { ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); - long token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); - protoOutputStream.write( - BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); + long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); protoOutputStream.end(token); - token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); - protoOutputStream.write( - BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1); + token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1); protoOutputStream.end(token); protoOutputStream.flush(); @@ -241,7 +237,7 @@ public final class BackgroundInstallControlServiceTest { // Read the file on the disk to verify var packagesInDisk = new SparseSetArray<>(); AtomicFile atomicFile = new AtomicFile(mFile); - try (FileInputStream fileInputStream = atomicFile.openRead()) { + try (FileInputStream fileInputStream = atomicFile.openRead()) { ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { @@ -249,23 +245,25 @@ public final class BackgroundInstallControlServiceTest { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - fail("Undefined field in proto: " - + protoInputStream.getFieldNumber()); + fail("Undefined field in proto: " + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -296,7 +294,7 @@ public final class BackgroundInstallControlServiceTest { // Read the file on the disk to verify var packagesInDisk = new SparseSetArray<>(); AtomicFile atomicFile = new AtomicFile(mFile); - try (FileInputStream fileInputStream = atomicFile.openRead()) { + try (FileInputStream fileInputStream = atomicFile.openRead()) { ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { @@ -304,23 +302,25 @@ public final class BackgroundInstallControlServiceTest { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - fail("Undefined field in proto: " - + protoInputStream.getFieldNumber()); + fail("Undefined field in proto: " + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -353,7 +353,7 @@ public final class BackgroundInstallControlServiceTest { // Read the file on the disk to verify var packagesInDisk = new SparseSetArray<>(); AtomicFile atomicFile = new AtomicFile(mFile); - try (FileInputStream fileInputStream = atomicFile.openRead()) { + try (FileInputStream fileInputStream = atomicFile.openRead()) { ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { @@ -361,23 +361,25 @@ public final class BackgroundInstallControlServiceTest { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - fail("Undefined field in proto: " - + protoInputStream.getFieldNumber()); + fail("Undefined field in proto: " + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -399,51 +401,55 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_permissionDenied() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, 0); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0); mTestLooper.dispatchAll(); - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); } @Test public void testHandleUsageEvent_permissionGranted() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, 0); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0); mTestLooper.dispatchAll(); - assertEquals(1, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + assertEquals( + 1, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); } @Test public void testHandleUsageEvent_ignoredEvent() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.USER_INTERACTION, - USER_ID_1, INSTALLER_NAME_1, 0); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent(UsageEvents.Event.USER_INTERACTION, USER_ID_1, INSTALLER_NAME_1, 0); mTestLooper.dispatchAll(); - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); } @Test public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -461,14 +467,18 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -486,16 +496,23 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_3); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -517,12 +534,13 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_firstNoneActivityResumed() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -535,27 +553,26 @@ public final class BackgroundInstallControlServiceTest { } @Test - public void testHandleUsageEvent_packageAddedNoUsageEvent() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedNoUsageEvent() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -572,27 +589,26 @@ public final class BackgroundInstallControlServiceTest { } @Test - public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedInsideTimeFrame() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -604,12 +620,16 @@ public final class BackgroundInstallControlServiceTest { // The 2 usage events make the package adding inside a time frame. // So it's not a background install. Thus, it's null for the return of // mBackgroundInstallControlService.getBackgroundInstalledPackages() - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -617,27 +637,26 @@ public final class BackgroundInstallControlServiceTest { } @Test - public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -650,12 +669,16 @@ public final class BackgroundInstallControlServiceTest { // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame, // it's a background install. Thus, it's not null for the return of // mBackgroundInstallControlService.getBackgroundInstalledPackages() - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -665,28 +688,28 @@ public final class BackgroundInstallControlServiceTest { assertEquals(1, packages.size()); assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1)); } + @Test - public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -700,12 +723,16 @@ public final class BackgroundInstallControlServiceTest { // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame, // it's a background install. Thus, it's not null for the return of // mBackgroundInstallControlService.getBackgroundInstalledPackages() - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_2, + INSTALLER_NAME_2, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -715,31 +742,31 @@ public final class BackgroundInstallControlServiceTest { assertEquals(1, packages.size()); assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1)); } + @Test - public void testHandleUsageEvent_packageAddedThroughAdb() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedThroughAdb() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the // initiatingPackageName used to be null but is now "com.android.shell". This test ensures // that the behavior is still the same for when the initiatingPackageName is null. - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ null, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ null, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); // b/265203007 when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -751,12 +778,16 @@ public final class BackgroundInstallControlServiceTest { // for ADB installs the initiatingPackageName used to be null, despite being detected // as a background install. Since we do not want to treat side-loaded apps as background // install getBackgroundInstalledPackages() is expected to return null - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -764,31 +795,31 @@ public final class BackgroundInstallControlServiceTest { var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages(); assertNull(packages); } + @Test - public void testHandleUsageEvent_packageAddedThroughAdb2() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedThroughAdb2() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the // initiatingPackageName used to be null but is now "com.android.shell". This test ensures // that the behavior is still the same after this change. - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ "com.android.shell", - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ "com.android.shell", + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); // b/265203007 when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -800,12 +831,16 @@ public final class BackgroundInstallControlServiceTest { // for ADB installs the initiatingPackageName is com.android.shell, despite being detected // as a background install. Since we do not want to treat side-loaded apps as background // install getBackgroundInstalledPackages() is expected to return null - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -813,6 +848,7 @@ public final class BackgroundInstallControlServiceTest { var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages(); assertNull(packages); } + @Test public void testPackageRemoved() { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); @@ -859,8 +895,7 @@ public final class BackgroundInstallControlServiceTest { packages.add(packageInfo2); var packageInfo3 = makePackageInfo(PACKAGE_NAME_3); packages.add(packageInfo3); - doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser( - any(), anyInt()); + doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(any(), anyInt()); var resultPackages = mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1); @@ -870,18 +905,30 @@ public final class BackgroundInstallControlServiceTest { assertFalse(resultPackages.getList().contains(packageInfo3)); } + @Test(expected = SecurityException.class) + public void enforceCallerPermissionsThrowsSecurityException() { + doThrow(new SecurityException("test")).when(mContext) + .enforceCallingOrSelfPermission(eq(GET_BACKGROUND_INSTALLED_PACKAGES), anyString()); + + mBackgroundInstallControlService.enforceCallerPermissions(); + } + + @Test + public void enforceCallerPermissionsDoesNotThrowSecurityException() { + //enforceCallerQueryPackagesPermissions do not throw + + mBackgroundInstallControlService.enforceCallerPermissions(); + } + /** * Mock a usage event occurring. * * @param usageEventId id of a usage event - * @param userId user id of a usage event - * @param pkgName package name of a usage event - * @param timestamp timestamp of a usage event + * @param userId user id of a usage event + * @param pkgName package name of a usage event + * @param timestamp timestamp of a usage event */ - private void generateUsageEvent(int usageEventId, - int userId, - String pkgName, - long timestamp) { + private void generateUsageEvent(int usageEventId, int userId, String pkgName, long timestamp) { Event event = new Event(usageEventId, timestamp); event.mPackage = pkgName; mUsageEventListener.onUsageEvent(userId, event); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 080548520b0c..81df597f3f33 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -157,6 +157,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return mMockDevicePolicyManager; case Context.APP_SEARCH_SERVICE: case Context.ROLE_SERVICE: + case Context.APP_OPS_SERVICE: // RoleManager is final and cannot be mocked, so we only override the inject // accessor methods in ShortcutService. return getTestContext().getSystemService(name); diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index 9d56a36196bb..5e11e17f9414 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -18,6 +18,7 @@ package com.android.server.rollback; import static com.google.common.truth.Truth.assertThat; +import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.util.SparseIntArray; @@ -81,7 +82,8 @@ public class RollbackStoreTest { + "'installedUsers':[55,79]," + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello'," + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}]," - + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z'," + + "'committedSessionId':45654465, 'rollbackImpactLevel':1}," + + "'timestamp':'2019-10-01T12:29:08.855Z'," + "'originalSessionId':567,'state':'enabling','apkSessionId':-1," + "'restoreUserDataInProgress':true, 'userId':0," + "'installerPackageName':'some.installer'}"; @@ -138,6 +140,8 @@ public class RollbackStoreTest { assertThat(rollback.getOriginalSessionId()).isEqualTo(567); assertThat(rollback.info.getRollbackId()).isEqualTo(ID); assertThat(rollback.info.getPackages()).isEmpty(); + assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo( + PackageManager.ROLLBACK_USER_IMPACT_LOW); assertThat(rollback.isEnabling()).isTrue(); assertThat(rollback.getExtensionVersions().toString()) .isEqualTo(extensionVersions.toString()); @@ -158,6 +162,8 @@ public class RollbackStoreTest { assertThat(rollback.info.getRollbackId()).isEqualTo(ID); assertThat(rollback.info.getPackages()).isEmpty(); + assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo( + PackageManager.ROLLBACK_USER_IMPACT_LOW); assertThat(rollback.isEnabling()).isTrue(); assertThat(rollback.getExtensionVersions().toString()) .isEqualTo(extensionVersions.toString()); @@ -175,6 +181,7 @@ public class RollbackStoreTest { origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2)); origRb.info.getCausePackages().add(new VersionedPackage("com.pack.age", 99)); origRb.info.setCommittedSessionId(123456); + origRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH); PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("com.made.up", 18), @@ -226,6 +233,7 @@ public class RollbackStoreTest { expectedRb.info.getCausePackages().add(new VersionedPackage("hello", 23)); expectedRb.info.getCausePackages().add(new VersionedPackage("something", 999)); expectedRb.info.setCommittedSessionId(45654465); + expectedRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH); PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55), new VersionedPackage("blah1", 50), new ArrayList<>(), new ArrayList<>(), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index ea948ca0e28b..863cda4905f1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -118,12 +118,19 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // Constructors that should be used to create instances of specific classes. Overrides scoring. private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS; + // Setter methods that receive String parameters, but where those Strings represent Uris + // (and are visited/validated). + private static final ImmutableSet<Method> SETTERS_WITH_STRING_AS_URI; + static { try { PREFERRED_CONSTRUCTORS = ImmutableMap.of( Notification.Builder.class, Notification.Builder.class.getConstructor(Context.class, String.class)); + SETTERS_WITH_STRING_AS_URI = ImmutableSet.of( + Person.Builder.class.getMethod("setUri", String.class)); + EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder() .put(RemoteViews.class, // b/245950570: Tries to connect to service and will crash. @@ -257,7 +264,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { @Nullable Class<?> styleClass, @Nullable Class<?> extenderClass, @Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) { SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context); - Set<Class<?>> excludedClasses = includeRemoteViews + ImmutableSet<Class<?>> excludedClasses = includeRemoteViews ? ImmutableSet.of() : ImmutableSet.of(RemoteViews.class); Location location = Location.root(Notification.Builder.class); @@ -294,7 +301,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static Object generateObject(Class<?> clazz, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { if (excludingClasses.contains(clazz)) { throw new IllegalArgumentException( String.format("Asked to generate a %s but it's part of the excluded set (%s)", @@ -369,7 +376,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static Object constructEmpty(Class<?> clazz, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { Constructor<?> bestConstructor; if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) { // Use the preferred constructor. @@ -431,7 +438,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static void invokeAllSetters(Object instance, Location where, boolean allOverloads, - boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes, + boolean includingVoidMethods, ImmutableSet<Class<?>> excludingParameterTypes, SpecialParameterGenerator specialGenerator) { for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where, allOverloads, includingVoidMethods, excludingParameterTypes)) { @@ -462,24 +469,34 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static Object[] generateParameters(Executable executable, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable) + " in " + where); Type[] parameterTypes = executable.getGenericParameterTypes(); Object[] parameterValues = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { - parameterValues[i] = generateParameter( - parameterTypes[i], + boolean generateUriAsString = false; + Type parameterType = parameterTypes[i]; + if (SETTERS_WITH_STRING_AS_URI.contains(executable) + && parameterType.equals(String.class)) { + generateUriAsString = true; + } + Object value = generateParameter( + generateUriAsString ? Uri.class : parameterType, where.plus(executable, String.format("[%d,%s]", i, parameterTypes[i].getTypeName())), excludingClasses, specialGenerator); + if (generateUriAsString) { + value = ((Uri) value).toString(); + } + parameterValues[i] = value; } return parameterValues; } private static Object generateParameter(Type parameterType, Location where, - Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { if (parameterType instanceof Class<?> parameterClass) { return generateObject( parameterClass, @@ -487,7 +504,8 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { excludingClasses, specialGenerator); } else if (parameterType instanceof ParameterizedType parameterizedType) { - if (parameterizedType.getRawType().equals(List.class) + if ((parameterizedType.getRawType().equals(List.class) + || parameterizedType.getRawType().equals(ArrayList.class)) && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) { ArrayList listValue = new ArrayList(); for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) { @@ -503,12 +521,14 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static class ReflectionUtils { - static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) { - return Arrays.stream(containerClass.getDeclaredClasses()) - .filter( - innerClass -> clazz.isAssignableFrom(innerClass) - && !Modifier.isAbstract(innerClass.getModifiers())) - .collect(Collectors.toSet()); + static ImmutableSet<Class<?>> getConcreteSubclasses(Class<?> clazz, + Class<?> containerClass) { + return ImmutableSet.copyOf( + Arrays.stream(containerClass.getDeclaredClasses()) + .filter( + innerClass -> clazz.isAssignableFrom(innerClass) + && !Modifier.isAbstract(innerClass.getModifiers())) + .collect(Collectors.toSet())); } static String methodToString(Executable executable) { @@ -611,9 +631,16 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } private static class SpecialParameterGenerator { + + private static final ImmutableSet<Class<?>> INTERESTING_CLASSES_WITH_SPECIAL_GENERATION = + ImmutableSet.of(Uri.class, Icon.class, Intent.class, PendingIntent.class, + RemoteViews.class); + private static final ImmutableSet<Class<?>> INTERESTING_CLASSES = - ImmutableSet.of(Person.class, Uri.class, Icon.class, Intent.class, - PendingIntent.class, RemoteViews.class); + new ImmutableSet.Builder<Class<?>>() + .addAll(INTERESTING_CLASSES_WITH_SPECIAL_GENERATION) + .add(Person.class) // Constructed via reflection, but high-score. + .build(); private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of(); private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES = @@ -637,7 +664,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } static boolean canGenerate(Class<?> clazz) { - return INTERESTING_CLASSES.contains(clazz) + return INTERESTING_CLASSES_WITH_SPECIAL_GENERATION.contains(clazz) || MOCKED_CLASSES.contains(clazz) || clazz.equals(Context.class) || clazz.equals(Bundle.class) @@ -672,17 +699,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { return Icon.createWithContentUri(iconUri); } - if (clazz == Person.class) { - // TODO(b/310189261): Person.setUri takes a string instead of a URI. We should - // find a way to use the SpecialParameterGenerator instead of this custom one. - Uri personUri = generateUri( - where.plus(Person.Builder.class).plus("setUri", String.class)); - Uri iconUri = generateUri(where.plus(Person.Builder.class).plus("setIcon", - Icon.class).plus(Icon.class).plus("createWithContentUri", Uri.class)); - return new Person.Builder().setUri(personUri.toString()).setIcon( - Icon.createWithContentUri(iconUri)).setName("John Doe").build(); - } - if (clazz == Intent.class) { return new Intent("action", generateUri(where.plus(Intent.class))); } 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 1c57623442c5..29faed195b4e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -132,8 +132,10 @@ import org.junit.runner.RunWith; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -563,6 +565,86 @@ public class ActivityStarterTests extends WindowTestsBase { return Pair.create(splitPrimaryActivity, splitSecondActivity); } + /** + * This test ensures that if the intent is being delivered to a desktop mode unfocused task + * while it is already on top, reports it as delivering to top. + */ + @Test + public void testDesktopModeDeliverToTop() { + final ActivityStarter starter = prepareStarter( + FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, + false /* mockGetRootTask */); + final List<ActivityRecord> activities = createActivitiesInDesktopMode(); + + // Set focus back to the first task. + activities.get(0).moveFocusableActivityToTop("testDesktopModeDeliverToTop"); + + // Start activity and delivered new intent. + starter.getIntent().setComponent(activities.get(3).mActivityComponent); + doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any()); + final int result = starter.setReason("testDesktopModeDeliverToTop").execute(); + + // Ensure result is delivering intent to top. + assertEquals(START_DELIVERED_TO_TOP, result); + } + + /** + * This test ensures that if the intent is being delivered to a desktop mode unfocused task + * reports it is brought to front instead of delivering to top. + */ + @Test + public void testDesktopModeTaskToFront() { + final ActivityStarter starter = prepareStarter( + FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false); + final List<ActivityRecord> activities = createActivitiesInDesktopMode(); + final ActivityRecord desktopModeFocusActivity = activities.get(0); + final ActivityRecord desktopModeReusableActivity = activities.get(1); + final ActivityRecord desktopModeTopActivity = new ActivityBuilder(mAtm).setCreateTask(true) + .setParentTask(desktopModeReusableActivity.getRootTask()).build(); + assertTrue(desktopModeTopActivity.inMultiWindowMode()); + + // Let first stack has focus. + desktopModeFocusActivity.moveFocusableActivityToTop("testDesktopModeTaskToFront"); + + // Start activity and delivered new intent. + starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent); + doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any()); + final int result = starter.setReason("testDesktopModeMoveToFront").execute(); + + // Ensure result is moving task to front. + assertEquals(START_TASK_TO_FRONT, result); + } + + /** Returns 4 activities. */ + private List<ActivityRecord> createActivitiesInDesktopMode() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + List<ActivityRecord> activityRecords = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds()); + bounds.offset(20 * i, 20 * i); + desktopOrganizer.createTask(bounds); + } + + for (int i = 0; i < 4; i++) { + activityRecords.add(new TaskBuilder(mSupervisor) + .setParentTask(desktopOrganizer.mTasks.get(i)) + .setCreateActivity(true) + .build() + .getTopMostActivity()); + } + + for (int i = 0; i < 4; i++) { + activityRecords.get(i).setVisibleRequested(true); + } + + for (int i = 0; i < 4; i++) { + assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask()); + } + + return activityRecords; + } + @Test public void testMoveVisibleTaskToFront() { final ActivityRecord activity = new TaskBuilder(mSupervisor) 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 114b9c3a68f2..c7c791337bb4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -20,6 +20,7 @@ import static android.app.AppOpsManager.OP_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +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_UNDEFINED; @@ -133,7 +134,9 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** Common base class for window manager unit test classes. */ class WindowTestsBase extends SystemServiceTestsBase { @@ -1892,6 +1895,55 @@ class WindowTestsBase extends SystemServiceTestsBase { } } + static class TestDesktopOrganizer extends WindowOrganizerTests.StubOrganizer { + final int mDesktopModeDefaultWidthDp = 840; + final int mDesktopModeDefaultHeightDp = 630; + final int mDesktopDensity = 284; + + final ActivityTaskManagerService mService; + final TaskDisplayArea mDefaultTDA; + List<Task> mTasks; + final DisplayContent mDisplay; + Rect mStableBounds; + + TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) { + mService = service; + mDefaultTDA = display.getDefaultTaskDisplayArea(); + mDisplay = display; + mService.mTaskOrganizerController.registerTaskOrganizer(this); + mTasks = new ArrayList<>(); + mStableBounds = display.getBounds(); + } + + TestDesktopOrganizer(ActivityTaskManagerService service) { + this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay()); + } + + public Task createTask(Rect bounds) { + Task task = mService.mTaskOrganizerController.createRootTask( + mDisplay, WINDOWING_MODE_FREEFORM, null); + task.setBounds(bounds); + mTasks.add(task); + spyOn(task); + return task; + } + + public Rect getDefaultDesktopTaskBounds() { + int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f); + int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f); + Rect outBounds = new Rect(); + + outBounds.set(0, 0, width, height); + // Center the task in stable bounds + outBounds.offset( + mStableBounds.centerX() - outBounds.centerX(), + mStableBounds.centerY() - outBounds.centerY() + ); + return outBounds; + } + + } + static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { return createTestWindowToken(type, dc, false /* persistOnEmpty */); } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 94c737d61b0a..a089f5c9d641 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -18,6 +18,7 @@ package android.telecom; import static android.Manifest.permission.MODIFY_PHONE_STATE; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -30,11 +31,15 @@ import android.os.Parcelable; import android.telephony.CarrierConfigManager; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.ArraySet; + +import com.android.internal.telephony.flags.Flags; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Represents a distinct method to place or receive a phone call. Apps which can place calls and @@ -491,6 +496,7 @@ public final class PhoneAccount implements Parcelable { private final Bundle mExtras; private boolean mIsEnabled; private String mGroupId; + private final Set<PhoneAccountHandle> mSimultaneousCallingRestriction; @Override public boolean equals(Object o) { @@ -508,7 +514,9 @@ public final class PhoneAccount implements Parcelable { Objects.equals(mShortDescription, that.mShortDescription) && Objects.equals(mSupportedUriSchemes, that.mSupportedUriSchemes) && areBundlesEqual(mExtras, that.mExtras) && - Objects.equals(mGroupId, that.mGroupId); + Objects.equals(mGroupId, that.mGroupId) + && Objects.equals(mSimultaneousCallingRestriction, + that.mSimultaneousCallingRestriction); } @Override @@ -516,7 +524,7 @@ public final class PhoneAccount implements Parcelable { return Objects.hash(mAccountHandle, mAddress, mSubscriptionAddress, mCapabilities, mHighlightColor, mLabel, mShortDescription, mSupportedUriSchemes, mSupportedAudioRoutes, - mExtras, mIsEnabled, mGroupId); + mExtras, mIsEnabled, mGroupId, mSimultaneousCallingRestriction); } /** @@ -537,6 +545,7 @@ public final class PhoneAccount implements Parcelable { private Bundle mExtras; private boolean mIsEnabled = false; private String mGroupId = ""; + private Set<PhoneAccountHandle> mSimultaneousCallingRestriction = null; /** * Creates a builder with the specified {@link PhoneAccountHandle} and label. @@ -787,6 +796,57 @@ public final class PhoneAccount implements Parcelable { } /** + * Restricts the ability of this {@link PhoneAccount} to ONLY support simultaneous calling + * with the other {@link PhoneAccountHandle}s in this Set. + * <p> + * If two or more {@link PhoneAccount}s support calling simultaneously, it means that + * Telecom allows the user to place additional outgoing calls and receive additional + * incoming calls using other {@link PhoneAccount}s while this PhoneAccount also has one or + * more active calls. + * <p> + * If this setter method is never called or cleared using + * {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all + * {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling. + * <p> + * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that + * were registered by the same application. Simultaneous calling across applications is + * always possible as long as the {@link Connection} supports hold. If a + * {@link PhoneAccountHandle} is included here and the package name doesn't match this + * application's package name, {@link TelecomManager#registerPhoneAccount(PhoneAccount)} + * will throw a {@link SecurityException}. + * + * @param handles The other {@link PhoneAccountHandle}s that support calling simultaneously + * with this one. Use {@link #clearSimultaneousCallingRestriction()} to remove the + * restriction and allow simultaneous calling to be supported across all + * {@link PhoneAccount}s registered by this package. + * @return The Builder used to set up the new PhoneAccount. + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + public @NonNull Builder setSimultaneousCallingRestriction( + @NonNull Set<PhoneAccountHandle> handles) { + if (handles == null) { + throw new IllegalArgumentException("the Set of PhoneAccountHandles must not be " + + "null"); + } + mSimultaneousCallingRestriction = handles; + return this; + } + + /** + * Clears a previously set simultaneous calling restriction set when + * {@link PhoneAccount.Builder#Builder(PhoneAccount)} is used to create a new PhoneAccount + * from an existing one. + * + * @return The Builder used to set up the new PhoneAccount. + * @see #setSimultaneousCallingRestriction(Set) + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + public @NonNull Builder clearSimultaneousCallingRestriction() { + mSimultaneousCallingRestriction = null; + return this; + } + + /** * Creates an instance of a {@link PhoneAccount} based on the current builder settings. * * @return The {@link PhoneAccount}. @@ -810,7 +870,8 @@ public final class PhoneAccount implements Parcelable { mExtras, mSupportedAudioRoutes, mIsEnabled, - mGroupId); + mGroupId, + mSimultaneousCallingRestriction); } } @@ -827,7 +888,8 @@ public final class PhoneAccount implements Parcelable { Bundle extras, int supportedAudioRoutes, boolean isEnabled, - String groupId) { + String groupId, + Set<PhoneAccountHandle> simultaneousCallingRestriction) { mAccountHandle = account; mAddress = address; mSubscriptionAddress = subscriptionAddress; @@ -841,6 +903,7 @@ public final class PhoneAccount implements Parcelable { mSupportedAudioRoutes = supportedAudioRoutes; mIsEnabled = isEnabled; mGroupId = groupId; + mSimultaneousCallingRestriction = simultaneousCallingRestriction; } public static Builder builder( @@ -1050,6 +1113,49 @@ public final class PhoneAccount implements Parcelable { return (mCapabilities & CAPABILITY_SELF_MANAGED) == CAPABILITY_SELF_MANAGED; } + /** + * If a restriction is set (see {@link #hasSimultaneousCallingRestriction()}), this method + * returns the Set of {@link PhoneAccountHandle}s that are allowed to support calls + * simultaneously with this {@link PhoneAccount}. + * <p> + * If this {@link PhoneAccount} is busy with one or more ongoing calls, a restriction is set on + * this PhoneAccount (see {@link #hasSimultaneousCallingRestriction()} to check), and a new + * incoming or outgoing call is received or placed on a PhoneAccount that is not in this Set, + * Telecom will reject or cancel the pending call in favor of keeping the ongoing call alive. + * <p> + * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that + * were registered by the same application. Simultaneous calling across applications is + * always possible as long as the {@link Connection} supports hold. + * + * @return the Set of {@link PhoneAccountHandle}s that this {@link PhoneAccount} supports + * simultaneous calls with. + * @throws IllegalStateException If there is no restriction set on this {@link PhoneAccount} + * and this method is called. Whether or not there is a restriction can be checked using + * {@link #hasSimultaneousCallingRestriction()}. + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + public @NonNull Set<PhoneAccountHandle> getSimultaneousCallingRestriction() { + if (mSimultaneousCallingRestriction == null) { + throw new IllegalStateException("This method can not be called if there is no " + + "simultaneous calling restriction. See #hasSimultaneousCallingRestriction"); + } + return mSimultaneousCallingRestriction; + } + + /** + * Whether or not this {@link PhoneAccount} contains a simultaneous calling restriction on it. + * + * @return {@code true} if this PhoneAccount contains a simultaneous calling restriction, + * {@code false} if it does not. Use {@link #getSimultaneousCallingRestriction()} to query which + * other {@link PhoneAccount}s support simultaneous calling with this one. + * @see #getSimultaneousCallingRestriction() for more information on how the sinultaneous + * calling restriction works. + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + public boolean hasSimultaneousCallingRestriction() { + return mSimultaneousCallingRestriction != null; + } + // // Parcelable implementation // @@ -1095,6 +1201,12 @@ public final class PhoneAccount implements Parcelable { out.writeBundle(mExtras); out.writeString(mGroupId); out.writeInt(mSupportedAudioRoutes); + if (mSimultaneousCallingRestriction == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeTypedList(mSimultaneousCallingRestriction.stream().toList()); + } } public static final @android.annotation.NonNull Creator<PhoneAccount> CREATOR @@ -1140,6 +1252,13 @@ public final class PhoneAccount implements Parcelable { mExtras = in.readBundle(); mGroupId = in.readString(); mSupportedAudioRoutes = in.readInt(); + if (in.readBoolean()) { + List<PhoneAccountHandle> list = new ArrayList<>(); + in.readTypedList(list, PhoneAccountHandle.CREATOR); + mSimultaneousCallingRestriction = new ArraySet<>(list); + } else { + mSimultaneousCallingRestriction = null; + } } @Override diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 9ec5f7a77b2c..67bb1f03cad6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6888,6 +6888,7 @@ public class TelephonyManager { } } + // TODO(b/316183370): replace all @code with @link in javadoc after feature is released /** * @return true if the current device is "voice capable". * <p> @@ -6901,7 +6902,10 @@ public class TelephonyManager { * PackageManager.FEATURE_TELEPHONY system feature, which is available * on any device with a telephony radio, even if the device is * data-only. - * @deprecated Replaced by {@link #isDeviceVoiceCapable()} + * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice + * capability may also be overridden by carriers for a given subscription. For voice capable + * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for + * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING) @Deprecated @@ -6923,9 +6927,10 @@ public class TelephonyManager { * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony * radio, even if the device is data-only. * <p> - * To check if a subscription is "voice capable", call method - * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with - * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}. + * Starting from Android 15, voice capability may also be overridden by carrier for a given + * subscription on a voice capable device. To check if a subscription is "voice capable", + * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if + * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ @@ -6943,7 +6948,10 @@ public class TelephonyManager { * <p> * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are * disabled when device doesn't support sms. - * @deprecated Replaced by {@link #isDeviceSmsCapable()} + * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS + * capability may also be overridden by carriers for a given subscription. For SMS capable + * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for + * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING) public boolean isSmsCapable() { @@ -6961,9 +6969,10 @@ public class TelephonyManager { * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are * disabled when device doesn't support SMS. * <p> - * To check if a subscription is "SMS capable", call method - * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with - * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}. + * Starting from Android 15, SMS capability may also be overridden by carriers for a given + * subscription on an SMS capable device. To check if a subscription is "SMS capable", + * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if + * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index acbf354bb4de..24296f4d9031 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3078,6 +3078,16 @@ interface ITelephony { in List<String> satelliteCountryCodes); /** + * This API can be used in only testing to override oem-enabled satellite provision status. + * + * @param reset {@code true} mean the overriding status should not be used, {@code false} + * otherwise. + * @param isProvisioned The overriding provision status. + * @return {@code true} if the provision status is set successfully, {@code false} otherwise. + */ + boolean setOemEnabledSatelliteProvisionStatus(in boolean reset, in boolean isProvisioned); + + /** * Test method to confirm the file contents are not altered. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java index 2bc056ee743f..cee27b6847ec 100644 --- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java +++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java @@ -16,6 +16,8 @@ package android.transparency.test.app; +import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -27,6 +29,7 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.compatibility.common.util.ShellIdentityUtils; import com.android.internal.os.IBinaryTransparencyService.AppInfo; import org.junit.Before; @@ -116,7 +119,12 @@ public class BinaryTransparencyTest { @Test public void testCollectAllSilentInstalledMbaInfo() { // Action - var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle()); + var appInfoList = + ShellIdentityUtils.invokeMethodWithShellPermissions( + mBt, + (Bt) -> + mBt.collectAllSilentInstalledMbaInfo(new Bundle()), + GET_BACKGROUND_INSTALLED_PACKAGES); // Verify assertThat(appInfoList).isNotEmpty(); // because we just installed from the host side diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index b05863188beb..256a4696b763 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -292,14 +292,16 @@ class InputManagerServiceTests { setVirtualMousePointerDisplayIdAndVerify(10) localService.setPointerIconVisible(false, 10) + verify(native).setPointerIconVisibility(10, false) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) localService.setMousePointerAccelerationEnabled(false, 10) - verify(native).setMousePointerAccelerationEnabled(eq(false)) + verify(native).setMousePointerAccelerationEnabled(10, false) service.onDisplayRemoved(10) + verify(native).setPointerIconVisibility(10, true) verify(native).displayRemoved(eq(10)) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) - verify(native).setMousePointerAccelerationEnabled(true) + verify(native).setMousePointerAccelerationEnabled(10, true) verifyNoMoreInteractions(native) // This call should not block because the virtual mouse pointer override was never removed. @@ -315,25 +317,26 @@ class InputManagerServiceTests { localService.setPointerIconVisible(false, 10) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) + verify(native).setPointerIconVisibility(10, false) localService.setMousePointerAccelerationEnabled(false, 10) - verify(native).setMousePointerAccelerationEnabled(eq(false)) + verify(native).setMousePointerAccelerationEnabled(10, false) localService.setPointerIconVisible(true, 10) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) + verify(native).setPointerIconVisibility(10, true) localService.setMousePointerAccelerationEnabled(true, 10) - verify(native).setMousePointerAccelerationEnabled(eq(true)) + verify(native).setMousePointerAccelerationEnabled(10, true) - // Verify that setting properties on a different display is not propagated until the - // pointer is moved to that display. localService.setPointerIconVisible(false, 20) + verify(native).setPointerIconVisibility(20, false) localService.setMousePointerAccelerationEnabled(false, 20) + verify(native).setMousePointerAccelerationEnabled(20, false) verifyNoMoreInteractions(native) clearInvocations(native) setVirtualMousePointerDisplayIdAndVerify(20) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) - verify(native).setMousePointerAccelerationEnabled(eq(false)) } @Test @@ -341,12 +344,13 @@ class InputManagerServiceTests { localService.setPointerIconVisible(false, 10) localService.setMousePointerAccelerationEnabled(false, 10) + verify(native).setPointerIconVisibility(10, false) + verify(native).setMousePointerAccelerationEnabled(10, false) verifyNoMoreInteractions(native) setVirtualMousePointerDisplayIdAndVerify(10) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) - verify(native).setMousePointerAccelerationEnabled(eq(false)) } @Test diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index cbdcb8869628..518183f9cd64 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -30,6 +30,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.UserManager; @@ -146,7 +147,8 @@ public class RollbackTest { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Upgrade from v1 to v2, with rollbacks enabled. - Install.single(TestApp.A2).setEnableRollback().commit(); + Install.single(TestApp.A2).setEnableRollback().setRollbackImpactLevel( + PackageManager.ROLLBACK_USER_IMPACT_HIGH).commit(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); // The app should now be available for rollback. @@ -154,6 +156,8 @@ public class RollbackTest { assertThat(available).isNotStaged(); assertThat(available).packagesContainsExactly( Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(available.getRollbackImpactLevel()).isEqualTo( + PackageManager.ROLLBACK_USER_IMPACT_HIGH); // We should not have received any rollback requests yet. // TODO: Possibly flaky if, by chance, some other app on device @@ -264,6 +268,8 @@ public class RollbackTest { RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); assertThat(rollbackB).packagesContainsExactly( Rollback.from(TestApp.B2).to(TestApp.B1)); + assertThat(rollbackB.getRollbackImpactLevel()).isEqualTo( + PackageManager.ROLLBACK_USER_IMPACT_LOW); // Register rollback committed receiver RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver(); |