Merge "Delete communal test factories in favor of kosmos" into main
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 44afbe6..14cce19 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.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 @@
*/
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 @@
/**
* 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 @@
}
};
+ /** 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 @@
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 @@
}
@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 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 @@
|| 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 @@
@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 @@
(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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 c49971e..11b24f5 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6172,8 +6172,6 @@
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 4e61b0a..3bddfe1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -195,6 +195,7 @@
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";
@@ -17875,6 +17876,7 @@
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 +22155,17 @@
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 +23319,8 @@
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";
@@ -42218,9 +42223,11 @@
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 +42268,14 @@
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 +46956,7 @@
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/test-current.txt b/core/api/test-current.txt
index a866a34..5d271cc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -156,6 +156,7 @@
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
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b2c6475..9d20f3c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -712,6 +712,8 @@
/** @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 4b2e93f..d8d136a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1533,10 +1533,11 @@
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;
@@ -2373,10 +2374,11 @@
"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 =
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 0000000..029b93a
--- /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/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 7c5d305..5bfc012 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -77,4 +77,11 @@
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/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 4fc5131..5056557 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -31,6 +31,10 @@
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 f17039b..e729063 100644
--- a/core/java/android/os/VintfRuntimeInfo.java
+++ b/core/java/android/os/VintfRuntimeInfo.java
@@ -28,6 +28,10 @@
private VintfRuntimeInfo() {}
+ static {
+ System.loadLibrary("vintf_jni");
+ }
+
/**
* @return /sys/fs/selinux/policyvers, via security_policyvers() native call
*
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index ce38bb8..e6d8fd0 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,6 +31,7 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
+import android.os.Binder;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -69,12 +71,17 @@
@Nullable
static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
- ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
- if (defaultPaymentApp == null) {
- return null;
+ String defaultAppPackageName = getDefaultWalletApp(context);
+
+ if (defaultAppPackageName == null) {
+ ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
+ if (defaultPaymentApp == null) {
+ return null;
+ }
+ defaultAppPackageName = defaultPaymentApp.getPackageName();
}
- ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
+ ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName);
if (serviceInfo == null) {
return null;
}
@@ -92,6 +99,20 @@
return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata);
}
+ private static String getDefaultWalletApp(Context context) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+ List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
+ return roleHolders.isEmpty() ? null : roleHolders.get(0);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return null;
+ }
+
private static ComponentName getDefaultPaymentApp(Context context) {
ContentResolver cr = context.getContentResolver();
String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index eca848a..1ea80f1 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -152,7 +152,8 @@
/** @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 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 @@
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 @@
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 @@
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 @@
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 2175b47..224e5d8 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -100,9 +100,25 @@
// 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 @@
}
}
mTabs = tabStops;
- mAddedWidthForJustify = 0;
+ mAddedWordSpacingInPx = 0;
mIsJustifying = false;
mEllipsisStart = ellipsisStart != ellipsisEnd ? ellipsisStart : 0;
@@ -274,19 +290,42 @@
* 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 @@
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 @@
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 @@
@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/jni/Android.bp b/core/jni/Android.bp
index c8fd246..656cc3e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -188,8 +188,6 @@
"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 @@
"libdmabufinfo",
"libgif",
"libgui_window_info_static",
+ "libkernelconfigs",
"libseccomp_policy",
"libgrallocusage",
"libscrypt_static",
@@ -350,7 +349,6 @@
"libnativeloader_lazy",
"libmemunreachable",
"libhidlbase",
- "libvintf",
"libnativedisplay",
"libnativewindow",
"libdl",
@@ -458,8 +456,25 @@
// (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 7a16318..aa63f4f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,8 +152,6 @@
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 @@
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 de1ce4e..1504a00 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 @@
} 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 477bd09..734b5f4 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 a5b2f65..ce4a337 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 @@
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 @@
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 b0271b9..7c2f588 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(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 210dc89..769fa72 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 0000000..3b29e30
--- /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 a3d5cf6..52cf679 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.
@@ -5695,6 +5702,7 @@
@hide -->
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
android:protectionLevel="signature|installer|module" />
+ <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<!-- @SystemApi Allows an application to manage the holders of roles associated with default
applications.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5be29a6..9e14006 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 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. 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 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 d12ef2b..ef12d8f 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 bbac69f..4f9b269 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.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 @@
@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 @@
}
@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 4841711..4cda26d 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 @@
}
@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 @@
}
@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 @@
}
@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/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
new file mode 100644
index 0000000..a525615
--- /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 a31992c..8ae5669 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -53,7 +53,7 @@
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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 607b4bd..294b8ae 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -23,40 +23,48 @@
<!-- 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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<!-- 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 @@
<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 @@
<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 @@
<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 @@
<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 0e3fb16..9707126 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 @@
/** @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 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 30d5edb..160f922 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 @@
}
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 @@
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 @@
}
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 ac2a1c8..adc7839 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 @@
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 @@
/** 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/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 9ce46d6..e9cd73b 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 @@
@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 @@
@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 @@
}
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 @@
@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 643e026..4ea71490 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.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 @@
@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 @@
@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 @@
@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/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index 5f1279e..8c7c871 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -36,15 +36,15 @@
}
flag {
- name: "gnss_configuration_from_resource"
- namespace: "location"
- description: "Flag for GNSS configuration from resource"
- bug: "317734846"
-}
-
-flag {
name: "replace_future_elapsed_realtime_jni"
namespace: "location"
description: "Flag for replacing future elapsedRealtime in JNI"
bug: "314328533"
}
+
+flag {
+ name: "gnss_configuration_from_resource"
+ namespace: "location"
+ 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 2eec9b3..8dfa6be 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -754,15 +754,16 @@
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 16eaaea..2022427 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 @@
*/
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/LoudnessCodecConfigurator.java
deleted file mode 100644
index aadd783..0000000
--- a/media/java/android/media/LoudnessCodecConfigurator.java
+++ /dev/null
@@ -1,431 +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.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.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-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;
-import java.util.concurrent.Executor;
-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)}.
- */
-@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-public class LoudnessCodecConfigurator {
- private static final String TAG = "LoudnessCodecConfigurator";
-
- /**
- * Listener used for receiving asynchronous loudness metadata updates.
- */
- @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public interface OnLoudnessCodecUpdateListener {
- /**
- * Contains the MediaCodec key/values that can be set directly to
- * configure the loudness of the handle's corresponding decoder (see
- * {@link MediaCodec#setParameters(Bundle)}).
- *
- * @param mediaCodec the mediaCodec that will receive the new parameters
- * @param codecValues contains loudness key/value pairs that can be set
- * 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.
- */
- @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- @NonNull
- default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
- @NonNull Bundle codecValues) {
- return codecValues;
- }
- }
-
- @NonNull private final LoudnessCodecDispatcher mLcDispatcher;
-
- private final Object mConfiguratorLock = new Object();
-
- @GuardedBy("mConfiguratorLock")
- private AudioTrack mAudioTrack;
-
- @GuardedBy("mConfiguratorLock")
- private final Executor mExecutor;
-
- @GuardedBy("mConfiguratorLock")
- private final OnLoudnessCodecUpdateListener mListener;
-
- @GuardedBy("mConfiguratorLock")
- private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>();
-
- /**
- * Creates a new instance of {@link LoudnessCodecConfigurator}
- *
- * <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)}.
- *
- * @return the {@link LoudnessCodecConfigurator} instance
- */
- @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public static @NonNull LoudnessCodecConfigurator create() {
- return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
- Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
- }
-
- /**
- * Creates a new instance of {@link LoudnessCodecConfigurator}
- *
- * <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()}.
- *
- * @param executor {@link Executor} to handle the callbacks
- * @param listener used for receiving updates
- *
- * @return the {@link LoudnessCodecConfigurator} instance
- */
- @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public static @NonNull LoudnessCodecConfigurator create(
- @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);
- }
-
- /**
- * Creates a new instance of {@link LoudnessCodecConfigurator}
- *
- * <p>This method should be used only in testing
- *
- * @param service interface for communicating with AudioService
- * @param executor {@link Executor} to handle the callbacks
- * @param listener used for receiving updates
- *
- * @return the {@link LoudnessCodecConfigurator} instance
- *
- * @hide
- */
- public static @NonNull LoudnessCodecConfigurator createForTesting(
- @NonNull IAudioService service,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnLoudnessCodecUpdateListener listener) {
- 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);
- }
-
- /** @hide */
- private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnLoudnessCodecUpdateListener listener) {
- 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);
- }
- }
-
- /**
- * 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.
- *
- * <p>No new element will be added if the passed {@code mediaCodec} was
- * previously added.
- *
- * @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.
- * @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) {
- final AtomicBoolean containsCodec = new AtomicBoolean(false);
- Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
- containsCodec.set(!codecSet.add(mc));
- return codecSet;
- });
- if (newSet == null) {
- newSet = new HashSet<>();
- newSet.add(mc);
- mMediaCodecs.put(mcInfo, newSet);
- }
- if (containsCodec.get()) {
- throw new IllegalArgumentException(
- "Loudness configurator already added " + mediaCodec);
- }
- if (mAudioTrack != null) {
- piid = mAudioTrack.getPlayerIId();
- }
- }
-
- if (piid != PLAYER_PIID_INVALID) {
- mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo);
- }
-
- return true;
- }
-
- /**
- * Removes the {@link MediaCodec} from receiving loudness updates.
- *
- * <p>This method can be called while asynchronous updates are live.
- *
- * <p>No elements will be removed if the passed mediaCodec was not added before.
- *
- * @param mediaCodec the element to remove for receiving asynchronous updates
- * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
- * does not contain loudness metadata or if it
- * was not added before
- */
- @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);
-
- mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec,
- "MediaCodec for removeMediaCodec cannot be null"));
-
- if (mcInfo == null) {
- throw new IllegalArgumentException("Could not extract codec loudness information");
- }
- synchronized (mConfiguratorLock) {
- if (mAudioTrack != null) {
- piid = mAudioTrack.getPlayerIId();
- }
- mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
- removedMc.set(mcs.remove(mediaCodec));
- if (mcs.isEmpty()) {
- // remove the entry
- removeInfo.set(true);
- return null;
- }
- return mcs;
- });
- if (!removedMc.get()) {
- throw new IllegalArgumentException(
- "Loudness configurator does not contain " + mediaCodec);
- }
- }
-
- if (piid != PLAYER_PIID_INVALID && removeInfo.get()) {
- mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo);
- }
- }
-
- /**
- * Gets synchronous loudness updates when no listener is required. The provided
- * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}.
- *
- * @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}
- *
- * @return the {@link Bundle} containing the current loudness parameters. Caller is
- * responsible to update the {@link MediaCodec}
- */
- @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- @NonNull
- public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack,
- @NonNull MediaCodec mediaCodec) {
- Objects.requireNonNull(audioTrack, "Passed audio track cannot be null");
-
- LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec);
- if (codecInfo == null) {
- return new Bundle();
- }
-
- return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo);
- }
-
- /** @hide */
- /*package*/ int getAssignedTrackPiid() {
- int piid = PLAYER_PIID_INVALID;
-
- synchronized (mConfiguratorLock) {
- if (mAudioTrack == null) {
- return piid;
- }
- piid = mAudioTrack.getPlayerIId();
- }
-
- return piid;
- }
-
- /** @hide */
- /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
- synchronized (mConfiguratorLock) {
- 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();
- final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo();
- if (codecInfo.isEncoder()) {
- // loudness info only for decoders
- Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation");
- return null;
- }
-
- try {
- final MediaFormat inputFormat = mediaCodec.getInputFormat();
- final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
- if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
- // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize
- // one of these two keys
- int aacProfile = -1;
- int profile = -1;
- try {
- aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
- } catch (NullPointerException e) {
- // does not contain KEY_AAC_PROFILE. do nothing
- }
- try {
- profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
- } catch (NullPointerException e) {
- // does not contain KEY_PROFILE. do nothing
- }
- if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
- || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
- lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
- } else {
- lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
- }
- } else {
- Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
- return null;
- }
-
- final MediaFormat outputFormat = mediaCodec.getOutputFormat();
- lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
- < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
- } catch (IllegalStateException e) {
- Log.e(TAG, "MediaCodec is not configured", e);
- return null;
- }
-
- return lci;
- }
-}
diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java
new file mode 100644
index 0000000..b3e5c52
--- /dev/null
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -0,0 +1,397 @@
+/*
+ * 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.media;
+
+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;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * 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 LoudnessCodecController implements SafeCloseable {
+ private static final String TAG = "LoudnessCodecController";
+
+ /**
+ * Listener used for receiving asynchronous loudness metadata updates.
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public interface OnLoudnessCodecUpdateListener {
+ /**
+ * Contains the MediaCodec key/values that can be set directly to
+ * configure the loudness of the handle's corresponding decoder (see
+ * {@link MediaCodec#setParameters(Bundle)}).
+ *
+ * @param mediaCodec the mediaCodec that will receive the new parameters
+ * @param codecValues contains loudness key/value pairs that can be set
+ * 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.
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ @NonNull
+ default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
+ @NonNull Bundle codecValues) {
+ return codecValues;
+ }
+ }
+
+ @NonNull
+ private final LoudnessCodecDispatcher mLcDispatcher;
+
+ private final Object mControllerLock = new Object();
+
+ private final int mSessionId;
+
+ @GuardedBy("mControllerLock")
+ private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>();
+
+ /**
+ * 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(int, Executor, OnLoudnessCodecUpdateListener)}.
+ *
+ * @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 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 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( int)}.
+ *
+ * @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 LoudnessCodecController} instance
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ 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");
+
+ 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 LoudnessCodecController}
+ *
+ * <p>This method should be used only in testing
+ *
+ * @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 LoudnessCodecController} instance
+ * @hide
+ */
+ public static @NonNull LoudnessCodecController createForTesting(
+ int sessionId,
+ @NonNull @CallbackExecutor Executor executor,
+ @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");
+
+ 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 LoudnessCodecController(@NonNull LoudnessCodecDispatcher lcDispatcher, int sessionId) {
+ mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null");
+ mSessionId = sessionId;
+ }
+
+ /**
+ * 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.
+ *
+ * @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.
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) {
+ final MediaCodec mc = Objects.requireNonNull(mediaCodec,
+ "MediaCodec for addMediaCodec cannot be null");
+ final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
+
+ if (mcInfo == null) {
+ Log.v(TAG, "Could not extract codec loudness information");
+ return false;
+ }
+ synchronized (mControllerLock) {
+ final AtomicBoolean containsCodec = new AtomicBoolean(false);
+ Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
+ containsCodec.set(!codecSet.add(mc));
+ return codecSet;
+ });
+ if (newSet == null) {
+ newSet = new HashSet<>();
+ newSet.add(mc);
+ mMediaCodecs.put(mcInfo, newSet);
+ }
+ if (containsCodec.get()) {
+ throw new IllegalArgumentException(
+ "Loudness controller already added " + mediaCodec);
+ }
+ }
+
+ mLcDispatcher.addLoudnessCodecInfo(mSessionId, mediaCodec.hashCode(),
+ mcInfo);
+
+ return true;
+ }
+
+ /**
+ * Removes the {@link MediaCodec} from receiving loudness updates.
+ *
+ * <p>This method can be called while asynchronous updates are live.
+ *
+ * <p>No elements will be removed if the passed mediaCodec was not added before.
+ *
+ * @param mediaCodec the element to remove for receiving asynchronous updates
+ * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
+ * does not contain loudness metadata or if it
+ * was not added before
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
+ LoudnessCodecInfo mcInfo;
+ AtomicBoolean removedMc = new AtomicBoolean(false);
+ AtomicBoolean removeInfo = new AtomicBoolean(false);
+
+ mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec,
+ "MediaCodec for removeMediaCodec cannot be null"));
+
+ if (mcInfo == null) {
+ throw new IllegalArgumentException("Could not extract codec loudness information");
+ }
+ synchronized (mControllerLock) {
+ mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
+ removedMc.set(mcs.remove(mediaCodec));
+ if (mcs.isEmpty()) {
+ // remove the entry
+ removeInfo.set(true);
+ return null;
+ }
+ return mcs;
+ });
+ if (!removedMc.get()) {
+ throw new IllegalArgumentException(
+ "Loudness controller does not contain " + mediaCodec);
+ }
+ }
+
+ if (removeInfo.get()) {
+ mLcDispatcher.removeLoudnessCodecInfo(mSessionId, mcInfo);
+ }
+ }
+
+ /**
+ * 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 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.
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ @NonNull
+ public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) {
+ Objects.requireNonNull(mediaCodec, "MediaCodec cannot be null");
+
+ LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec);
+ if (codecInfo == null) {
+ throw new IllegalArgumentException("MediaCodec does not have valid codec information");
+ }
+
+ 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);
+ }
+
+ /**
+ * Stops any loudness updates and frees up the resources.
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void release() {
+ close();
+ }
+
+ /** @hide */
+ @Override
+ public void close() {
+ synchronized (mControllerLock) {
+ mMediaCodecs.clear();
+ }
+ mLcDispatcher.stopLoudnessCodecUpdates(mSessionId);
+ }
+
+ /** @hide */
+ /*package*/ int getSessionId() {
+ return mSessionId;
+ }
+
+ /** @hide */
+ /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
+ synchronized (mControllerLock) {
+ return mMediaCodecs;
+ }
+ }
+
+ @Nullable
+ private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) {
+ LoudnessCodecInfo lci = new LoudnessCodecInfo();
+ final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo();
+ if (codecInfo.isEncoder()) {
+ // loudness info only for decoders
+ Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation");
+ return null;
+ }
+
+ try {
+ final MediaFormat inputFormat = mediaCodec.getInputFormat();
+ final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
+ if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
+ // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize
+ // one of these two keys
+ int aacProfile = -1;
+ int profile = -1;
+ try {
+ aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
+ } catch (NullPointerException e) {
+ // does not contain KEY_AAC_PROFILE. do nothing
+ }
+ try {
+ profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
+ } catch (NullPointerException e) {
+ // does not contain KEY_PROFILE. do nothing
+ }
+ if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
+ || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
+ lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
+ } else {
+ lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
+ }
+ } else {
+ Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
+ return null;
+ }
+
+ final MediaFormat outputFormat = mediaCodec.getOutputFormat();
+ lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
+ < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "MediaCodec is not configured", e);
+ return null;
+ }
+
+ return lci;
+ }
+}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index b546a81..46be54b 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_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 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
}
- 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 @@
}
/** @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 @@
}
/** @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 587e35b..5e40eee 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 @@
* <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 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 8e9c079..a0f8ae5 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1154,6 +1154,7 @@
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/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
deleted file mode 100644
index 74e5612..0000000
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ /dev/null
@@ -1,338 +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.loudnesscodecapitest;
-
-import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-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.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioTrack;
-import android.media.IAudioService;
-import android.media.LoudnessCodecConfigurator;
-import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
-import android.media.MediaCodec;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.util.Log;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-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
- * {@link IAudioService} without any real IPC interactions.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class LoudnessCodecConfiguratorTest {
- private static final String TAG = "LoudnessCodecConfiguratorTest";
-
- private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
- private static final int TEST_AUDIO_TRACK_BUFFER_SIZE = 2048;
- private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000;
- private static final int TEST_AUDIO_TRACK_CHANNELS = 2;
-
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Mock
- private IAudioService mAudioService;
-
- private LoudnessCodecConfigurator mLcc;
-
- @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();
- }
- }
-
- @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();
- }
- }
-
- @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();
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- mLcc.setAudioTrack(track);
-
- verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- anyList());
- } finally {
- mediaCodec.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setTrackNull_stopCodecUpdates() throws Exception {
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
-
- mLcc.setAudioTrack(null); // stops updates
- verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
- } finally {
- mediaCodec.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void addMediaCodecTwice_triggersIAE() throws Exception {
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec);
-
- assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
- } finally {
- mediaCodec.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
- final MediaCodec mediaCodec = MediaCodec.createDecoderByType("audio/mpeg");
-
- try {
- assertFalse(mLcc.addMediaCodec(mediaCodec));
- } finally {
- mediaCodec.release();
- }
- }
-
- @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());
- } finally {
- mediaCodec.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception {
- 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()), anyList());
-
- mLcc.addMediaCodec(mediaCodec2);
- verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
- } finally {
- mediaCodec1.release();
- mediaCodec2.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void removeMediaCodecAfterSetTrack_callsAudioServiceRemove() throws Exception {
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
- mLcc.removeMediaCodec(mediaCodec);
- verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
- } finally {
- mediaCodec.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception {
- 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()), anyList());
-
- assertThrows(IllegalArgumentException.class,
- () -> mLcc.removeMediaCodec(mediaCodec2));
- } finally {
- mediaCodec1.release();
- mediaCodec2.release();
- }
- }
-
- 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()
- .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4);
-
- MediaExtractor extractor;
- extractor = new MediaExtractor();
- try {
- extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
- testFd.getLength());
- assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
- MediaFormat format = extractor.getTrackFormat(0);
- String mime = format.getString(MediaFormat.KEY_MIME);
- assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
- final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
-
- Log.v(TAG, "configuring with " + format);
- mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
- return mediaCodec;
- } finally {
- testFd.close();
- extractor.release();
- }
- }
-}
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
new file mode 100644
index 0000000..4f6ede5
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.loudnesscodecapitest;
+
+import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.media.LoudnessCodecController;
+import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executors;
+
+/**
+ * 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 LoudnessCodecControllerTest {
+ private static final String TAG = "LoudnessCodecConfiguratorTest";
+
+ private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
+ private static final int TEST_AUDIO_TRACK_BUFFER_SIZE = 2048;
+ private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000;
+ private static final int TEST_AUDIO_TRACK_CHANNELS = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Mock
+ private IAudioService mAudioService;
+
+ private LoudnessCodecController mLcc;
+
+ private int mSessionId;
+
+ @Before
+ public void setUp() {
+ 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 createLcc_callsAudioServiceStart() throws Exception {
+ verify(mAudioService).startLoudnessCodecUpdates(eq(mSessionId));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
+ when(mAudioService.getLoudnessParams(any())).thenReturn(new PersistableBundle());
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.getLoudnessCodecParams(mediaCodec);
+
+ verify(mAudioService).getLoudnessParams(any());
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void release_stopCodecUpdates() throws Exception {
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.release(); // stops updats
+
+ verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void addMediaCodecTwice_triggersIAE() throws Exception {
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+
+ assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
+ final MediaCodec mediaCodec = MediaCodec.createDecoderByType("audio/mpeg");
+
+ try {
+ assertFalse(mLcc.addMediaCodec(mediaCodec));
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception {
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.removeMediaCodec(mediaCodec);
+
+ verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any());
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void addMediaCodec_callsAudioServiceAdd() throws Exception {
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void removeMediaCodec_callsAudioServiceRemove() throws Exception {
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
+
+ mLcc.removeMediaCodec(mediaCodec);
+ verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any());
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void removeWrongMediaCodec_triggersIAE() throws Exception {
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mLcc.removeMediaCodec(mediaCodec2));
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
+ }
+
+ private MediaCodec createAndConfigureMediaCodec() throws Exception {
+ AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext()
+ .getResources()
+ .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4);
+
+ MediaExtractor extractor;
+ extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+ assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+ MediaFormat format = extractor.getTrackFormat(0);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
+ final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
+
+ Log.v(TAG, "configuring with " + format);
+ mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ return mediaCodec;
+ } finally {
+ testFd.close();
+ extractor.release();
+ }
+ }
+}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 7573474..1046d8e9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -72,7 +72,7 @@
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 @@
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 40672a1..dc2a625 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -8,7 +8,6 @@
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 @@
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 @@
}
@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 bec62c5..63c3414 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -32,8 +32,8 @@
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 @@
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 c2b7075..584eb9a 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 @@
/**
* 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 68c16e6..11eb97b 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2820,13 +2820,12 @@
@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 @@
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 @@
/**
* Called on NFC WLC state changes
*/
- void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+ void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo);
}
/**
@@ -2945,12 +2944,12 @@
*/
@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 @@
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 8d79310..890cb09 100644
--- a/nfc/java/android/nfc/NfcWlcStateListener.java
+++ b/nfc/java/android/nfc/NfcWlcStateListener.java
@@ -36,7 +36,7 @@
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 @@
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 @@
}
@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 016431e..0000000
--- 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
similarity index 94%
rename from nfc/java/android/nfc/WlcLDeviceInfo.aidl
rename to nfc/java/android/nfc/WlcListenerDeviceInfo.aidl
index 33143fe..7f2ca54 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 0000000..45315f8
--- /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 ba8e354..b34c310 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 @@
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 @@
.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 @@
)
},
readOnly = true,
- enabled = enabled
+ enabled = enabled,
+ isError = errorMessage != null,
+ supportingText = {
+ if (errorMessage != null) {
+ Text(text = errorMessage)
+ }
+ }
)
if (options.isNotEmpty()) {
ExposedDropdownMenu(
@@ -98,9 +107,17 @@
.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 57012aa..931a6f1 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 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.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -57,13 +59,12 @@
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 @@
}
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 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 @@
return BluetoothProfile.LE_AUDIO;
}
- LeAudioProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LeAudioProfile(
+ Context context,
+ CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mContext = context;
mDeviceManager = deviceManager;
@@ -113,8 +115,7 @@
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 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 @@
}
/*
- * @hide
- */
+ * @hide
+ */
public boolean connect(BluetoothDevice device) {
if (mService == null) {
return false;
@@ -158,8 +163,8 @@
}
/*
- * @hide
- */
+ * @hide
+ */
public boolean disconnect(BluetoothDevice device) {
if (mService == null) {
return false;
@@ -174,6 +179,14 @@
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 @@
/**
* 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 @@
}
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 de21c54..9348705 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 @@
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 @@
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 @@
* <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 @@
}
// 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 @@
}
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 @@
// 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 @@
}
}
+ /** 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 @@
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/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 ccf119a..97c407c 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 @@
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 c8560c3..c300e0a 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 @@
@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 27b84b2..d30e333 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 @@
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 @@
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 cfe8c5d..73db175 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 @@
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 a0c2acc..cddbd1f 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 @@
@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 b3b6457..c6d612d 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 @@
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 c2680bc..725bdbd 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 @@
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 1d660d6..06e1258 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 @@
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/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 52305b1..05b5891 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 @@
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 562f96c..c143468 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 @@
@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 @@
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/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 2c3afb1..0b320a2 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 @@
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 @@
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 9bccf4e2..ce43d4e 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.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 @@
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 9daf186..199ffa6 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.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.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 @@
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 3f7e0df..d443851 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 @@
}
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 c7ab529..ff41ea2 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 @@
}
}
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 f1690daf..89e29cf 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.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.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 @@
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 f763a67..36b26a4 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 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.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 @@
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 fd2fd2f..1eaa060 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
@@ -58,7 +58,9 @@
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() {
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 aa15d0b..4595fbf 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 @@
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 74025fd..8f04ec38 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.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 @@
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 6fcb0c1..b120f87 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.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.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 @@
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 639114c..dddf648 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 @@
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 30b87bb..30ac344 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 @@
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 96d5774..4207a9c 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 @@
@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 8ee6d20..d05e98f 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 @@
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 a84b9fa..da60c18 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 @@
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 20653ca..995d6ac 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 @@
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 f3c3579..ccd7ed9 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 @@
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 7497ebd..46e1609 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 @@
@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 be523b8..d7a7941 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 @@
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 1cd764e..1e5ebd0 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 @@
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 @@
),
notifications = kosmos.notificationsPlaceholderViewModel,
)
+ }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -179,8 +182,8 @@
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 fc0df12..16cb623 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 @@
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 ede453d8..a16ce43 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 @@
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 5174502..e9a2a3b 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 @@
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 251daff..5ef095f 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 @@
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 ef2046d..ad4b98b 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 @@
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 e093859..3a38631 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 @@
@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 @@
@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 @@
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 607996d..6a2e317 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 @@
}
}
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 0000000..045c19e
--- /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 0000000..5e012ab
--- /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 0000000..d8a9a70
--- /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 0000000..dec9930
--- /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 0000000..ee4d05c
--- /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 3be9993..156c983 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 ee2a1ce..c630a7f 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/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6869bba..97999cc 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 @@
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/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index a103566..07705f3 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 @@
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 @@
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 @@
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 @@
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/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 3a247c5..3b8fb26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -20,6 +20,7 @@
import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
+import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE;
import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards;
import android.content.Intent;
@@ -42,7 +43,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,6 +54,7 @@
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -118,7 +119,8 @@
protected void handleSetListening(boolean listening) {
super.handleSetListening(listening);
if (listening) {
- mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE);
+ mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE,
+ DEFAULT_WALLET_APP_CHANGE);
if (!mController.getWalletClient().isWalletServiceAvailable()
|| !mController.getWalletClient().isWalletFeatureAvailable()) {
Log.i(TAG, "QAW service is unavailable, recreating the wallet client.");
@@ -201,7 +203,8 @@
@Override
protected void handleDestroy() {
super.handleDestroy();
- mController.unregisterWalletChangeObservers(DEFAULT_PAYMENT_APP_CHANGE);
+ mController.unregisterWalletChangeObservers(DEFAULT_PAYMENT_APP_CHANGE,
+ DEFAULT_WALLET_APP_CHANGE);
}
private class WalletCardRetriever implements
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 0000000..252945f
--- /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 e309c32..2b90e64 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 @@
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 e3c3139..8400fb0 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 @@
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 @@
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 bc38b53..a608be3 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 @@
*/
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 b2a7733..6de7a00 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.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 @@
)
.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 @@
(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 4cd877e..11a61a9 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 @@
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 @@
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 0aa95f8..4836abe 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 @@
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 e5a5695..f8858c5 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 @@
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 48bf7ac..a124196 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 @@
)
.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 @@
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 f44401b..77fd6be 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 @@
.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 de46a5e..0e67756 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 @@
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 @@
/**
* "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 @@
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 @@
// 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 @@
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 @@
* connection might cause more frequent checks.
*/
while (true) {
+ logBuffer.i {
+ "requestIsSatelliteCommunicationAllowedForCurrentLocation"
+ }
checkIsSatelliteAllowed()
delay(POLLING_INTERVAL_MS)
}
@@ -167,6 +186,8 @@
}
}
} else {
+ logBuffer.i { "Satellite manager is null" }
+
satelliteSupport.value = NotSupported
}
}
@@ -181,12 +202,21 @@
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 @@
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 @@
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 @@
}
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 @@
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 0000000..f5d0f6b
--- /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 0000000..59ac5f2
--- /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 0000000..6938d66
--- /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 0000000..0051161
--- /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 3b87bed..25a2c9d 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 @@
*
* 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 0000000..c663c37
--- /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/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index e031be2..e0228d9 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -17,10 +17,13 @@
package com.android.systemui.wallet.controller;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
+import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
import android.annotation.WorkerThread;
import android.app.PendingIntent;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -31,12 +34,12 @@
import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
import android.util.Log;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallet.ui.WalletActivity;
@@ -58,6 +61,7 @@
*/
public enum WalletChangeEvent {
DEFAULT_PAYMENT_APP_CHANGE,
+ DEFAULT_WALLET_APP_CHANGE,
WALLET_PREFERENCE_CHANGE,
}
@@ -71,9 +75,12 @@
private QuickAccessWalletClient mQuickAccessWalletClient;
private ContentObserver mWalletPreferenceObserver;
+ private RoleManager mRoleManager;
+ private OnRoleHoldersChangedListener mDefaultWalletAppObserver;
private ContentObserver mDefaultPaymentAppObserver;
private int mWalletPreferenceChangeEvents = 0;
private int mDefaultPaymentAppChangeEvents = 0;
+ private int mDefaultWalletAppChangeEvents = 0;
private boolean mWalletEnabled = false;
private long mQawClientCreatedTimeMillis;
@@ -89,6 +96,7 @@
mExecutor = executor;
mBgExecutor = bgExecutor;
mSecureSettings = secureSettings;
+ mRoleManager = mContext.getSystemService(RoleManager.class);
mQuickAccessWalletClient = quickAccessWalletClient;
mClock = clock;
mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
@@ -122,6 +130,8 @@
setupWalletPreferenceObserver();
} else if (event == DEFAULT_PAYMENT_APP_CHANGE) {
setupDefaultPaymentAppObserver(cardsRetriever);
+ } else if (event == DEFAULT_WALLET_APP_CHANGE) {
+ setupDefaultWalletAppObserver(cardsRetriever);
}
}
}
@@ -141,6 +151,12 @@
if (mDefaultPaymentAppChangeEvents == 0) {
mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver);
}
+ } else if (event == DEFAULT_WALLET_APP_CHANGE && mDefaultWalletAppObserver != null) {
+ mDefaultWalletAppChangeEvents--;
+ if (mDefaultWalletAppChangeEvents == 0) {
+ mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mDefaultWalletAppObserver,
+ UserHandle.ALL);
+ }
}
}
}
@@ -300,6 +316,25 @@
mDefaultPaymentAppChangeEvents++;
}
+ private void setupDefaultWalletAppObserver(
+ QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
+ if (mDefaultWalletAppObserver == null) {
+ mDefaultWalletAppObserver = (roleName, user) -> {
+ if (!roleName.equals(RoleManager.ROLE_WALLET)) {
+ return;
+ }
+ mExecutor.execute(() -> {
+ reCreateWalletClient();
+ updateWalletPreference();
+ queryWalletCards(cardsRetriever);
+ });
+ };
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(mExecutor,
+ mDefaultWalletAppObserver, UserHandle.ALL);
+ }
+ mDefaultWalletAppChangeEvents++;
+ }
+
private void setupWalletPreferenceObserver() {
if (mWalletPreferenceObserver == null) {
mWalletPreferenceObserver = new ContentObserver(null /* handler */) {
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
index 75df1bd..eb4ff17 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -84,7 +84,9 @@
walletController.setupWalletChangeObservers(
callback,
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ QuickAccessWalletController.WalletChangeEvent
+ .DEFAULT_PAYMENT_APP_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
)
walletController.updateWalletPreference()
walletController.queryWalletCards(callback, MAX_CARDS)
@@ -94,7 +96,9 @@
QuickAccessWalletController.WalletChangeEvent
.WALLET_PREFERENCE_CHANGE,
QuickAccessWalletController.WalletChangeEvent
- .DEFAULT_PAYMENT_APP_CHANGE
+ .DEFAULT_PAYMENT_APP_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent
+ .DEFAULT_WALLET_APP_CHANGE
)
}
}
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 b8f9583..1ba269e 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.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 @@
class WallpaperRepositoryImpl
@Inject
constructor(
- @Application scope: CoroutineScope,
+ @Background scope: CoroutineScope,
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
private val wallpaperManager: WallpaperManager,
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 5e5273b..bc9a0a5 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 @@
@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/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 1f8cc54..6785de93 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 @@
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 @@
val roaming: Boolean,
val name: String,
val slice: Boolean,
+ val ntn: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -205,7 +207,8 @@
"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 @@
roaming: Boolean? = null,
name: String? = null,
slice: Boolean? = null,
+ ntn: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -232,6 +236,7 @@
roaming = roaming ?: this.roaming,
name = name ?: this.name,
slice = slice ?: this.slice,
+ ntn = ntn ?: this.ntn,
)
}
@@ -262,6 +267,7 @@
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 @@
roaming.first(),
names.first(),
slice.first(),
+ ntn.first(),
)
val tail =
@@ -312,7 +319,8 @@
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 03814bd..b958f35 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 @@
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 9d6f315..9855651 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.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 @@
}
@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 02e6fd5..0e0d489 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 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 @@
Optional.empty(),
dispatcher,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
systemClock,
)
@@ -100,6 +102,45 @@
}
@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 @@
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 0000000..21c038a
--- /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 0000000..ca9df57
--- /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/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index b820ca6..1afe56f 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.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 @@
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 @@
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 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 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 @@
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/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index a9c8ec7..32d572e 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 @@
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/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index b5130a1..ced10fb 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -34,3 +34,10 @@
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 6d0915b..5c93991 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.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 @@
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 @@
}
}
+ 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 @@
}
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);
- }
- }
+ 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 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);
+ @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 @@
}
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);
- }
- }
+ 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 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));
- }
- });
+ @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 @@
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 @@
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/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c9bd0b4..86894fd 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 @@
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 c96c2ff..3487ae3 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.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 @@
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 4cbee2b..9f7c07e 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 @@
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_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.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.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 @@
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 9b0afc4..01f770b 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 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.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.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 @@
/**
* 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 @@
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 @@
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 SparseArray<Set<LoudnessCodecInfo>> mStartedPiids = new SparseArray<>();
+ private final HashMap<LoudnessTrackId, Set<Integer>> mStartedConfigPiids =
+ new HashMap<>();
+
+ /** Contains for each LoudnessTrackId a set of started coudec infos. */
+ @GuardedBy("mLock")
+ private final HashMap<LoudnessTrackId, Set<LoudnessCodecInfo>> mStartedConfigInfo =
+ new HashMap<>();
/** Contains the current device id assignment for each piid. */
@GuardedBy("mLock")
@@ -169,10 +180,12 @@
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 @@
}
private LoudnessCodecInputProperties(int metadataType,
- boolean isDownmixing,
- @DeviceSplRange int deviceSplRange) {
+ boolean isDownmixing,
+ @DeviceSplRange int deviceSplRange) {
mMetadataType = metadataType;
mIsDownmixing = isDownmixing;
mDeviceSplRange = deviceSplRange;
@@ -273,6 +286,50 @@
}
}
+ /**
+ * 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,121 +347,161 @@
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);
- }
- }
-
- void stopLoudnessCodecUpdates(int piid) {
- if (DEBUG) {
- Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid);
- }
-
- synchronized (mLock) {
- if (!mStartedPiids.contains(piid)) {
- Log.w(TAG, "Loudness updates are already stopped for piid " + piid);
- return;
- }
- mStartedPiids.remove(piid);
-
- sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
- mPiidToDeviceIdCache.delete(piid);
- mPiidToPidCache.delete(piid);
- }
- }
-
- void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo info) {
- if (DEBUG) {
- Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " mcHash " + mediaCodecHash + " info "
- + info);
- }
-
- Set<LoudnessCodecInfo> infoSet;
- synchronized (mLock) {
- if (!mStartedPiids.contains(piid)) {
- Log.w(TAG, "Cannot add new loudness info for stopped piid " + piid);
- return;
- }
-
- infoSet = mStartedPiids.get(piid);
- 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);
- }
+ 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 removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
+ void stopLoudnessCodecUpdates(int sessionId) {
+ int pid = Binder.getCallingPid();
if (DEBUG) {
- Log.d(TAG, "removeLoudnessCodecInfo: piid " + piid + " info " + codecInfo);
+ Log.d(TAG,
+ "stopLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
}
+
+ 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)) {
+ Log.w(TAG, "Loudness updates are already stopped config: " + config);
return;
}
- final Set<LoudnessCodecInfo> infoSet = mStartedPiids.get(piid);
- infoSet.remove(codecInfo);
+
+ 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);
}
}
- PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
+ void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+ LoudnessCodecInfo info) {
+ int pid = Binder.getCallingPid();
if (DEBUG) {
- Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo);
+ Log.d(TAG, "addLoudnessCodecInfo: sessionId " + sessionId
+ + " mcHash " + mediaCodecHash + " info " + info + " pid " + pid);
}
- 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;
- }
+ final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
+ Set<LoudnessCodecInfo> infoSet;
+ Set<Integer> piids;
+ synchronized (mLock) {
+ if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
+ config)) {
+ Log.w(TAG, "Cannot add new loudness info for stopped config " + config);
+ return;
+ }
+
+ piids = mStartedConfigPiids.get(config);
+ infoSet = mStartedConfigInfo.get(config);
+ infoSet.add(info);
+ }
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ 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) {
- return getCodecBundle_l(info, codecInfo);
+ // 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 sessionId, LoudnessCodecInfo codecInfo) {
+ if (DEBUG) {
+ Log.d(TAG, "removeLoudnessCodecInfo: session ID" + sessionId + " info " + codecInfo);
+ }
+
+ int pid = Binder.getCallingPid();
+ final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
+ synchronized (mLock) {
+ if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
+ config)) {
+ Log.w(TAG, "Cannot remove loudness info for stopped config " + config);
+ return;
+ }
+ final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
+ if (!codecInfos.remove(codecInfo)) {
+ Log.w(TAG, "Could not find to remove codecInfo " + codecInfo);
+ }
+ }
+ }
+
+ PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) {
+ if (DEBUG) {
+ 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);
+ }
}
// return empty Bundle
@@ -444,13 +541,21 @@
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 @@
// 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 @@
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 @@
}
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 @@
}
@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 @@
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 f8a9867..7f04628 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 @@
*
* @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 535b7b7..d8dfa60 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 @@
}
@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 0045d44..da4e515 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 @@
* @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 0f01510..b0649b9 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.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 @@
}
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 470dc4b..22e399c 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 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 @@
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 @@
}
}
+ 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 a529fb9..5ddddda 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 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 @@
}
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 @@
}
}
+ 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 0af6e40..f5c4529 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.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 @@
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 @@
}
}
+ 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 145885d..f7e8123 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.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 @@
}
try {
- mCancellationSignal = doAuthenticate();
+ if (Flags.deHidl()) {
+ startAuthentication();
+ } else {
+ mCancellationSignal = doAuthenticate();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(
@@ -353,6 +358,51 @@
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 3aab7b3..a7fb774 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.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 @@
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 @@
}
}
+ 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 bf5011d..3fb9223 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.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 @@
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 @@
}
}
+ 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/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index a763251..542165d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -46,7 +46,7 @@
private static final String TAG = "InputMethodInfoUtils";
/**
- * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
+ * Used in {@link #getFallbackLocaleForDefaultIme(List, Context)} to find the fallback IMEs
* that are mainly used until the system becomes ready. Note that {@link Locale} in this array
* is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
* doesn't automatically match {@code Locale("en", "IN")}.
@@ -64,7 +64,7 @@
@NonNull
private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
- InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+ InputMethodListBuilder fillImes(List<InputMethodInfo> imis, Context context,
boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
String requiredSubtypeMode) {
for (int i = 0; i < imis.size(); ++i) {
@@ -77,7 +77,7 @@
return this;
}
- InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
+ InputMethodListBuilder fillAuxiliaryImes(List<InputMethodInfo> imis, Context context) {
// If one or more auxiliary input methods are available, OK to stop populating the list.
for (final InputMethodInfo imi : mInputMethodSet) {
if (imi.isAuxiliaryIme()) {
@@ -118,7 +118,7 @@
}
private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
- ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+ List<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
@Nullable Locale fallbackLocale) {
// Once the system becomes ready, we pick up at least one keyboard in the following order.
// Secondary users fall into this category in general.
@@ -167,7 +167,7 @@
}
static ArrayList<InputMethodInfo> getDefaultEnabledImes(
- Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
+ Context context, List<InputMethodInfo> imis, boolean onlyMinimum) {
final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
// We will primarily rely on the system locale, but also keep relying on the fallback locale
// as a last resort.
@@ -186,7 +186,7 @@
}
static ArrayList<InputMethodInfo> getDefaultEnabledImes(
- Context context, ArrayList<InputMethodInfo> imis) {
+ Context context, List<InputMethodInfo> imis) {
return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
}
@@ -283,7 +283,7 @@
}
@Nullable
- private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+ private static Locale getFallbackLocaleForDefaultIme(List<InputMethodInfo> imis,
Context context) {
// At first, find the fallback locale from the IMEs that are declared as "default" in the
// current locale. Note that IME developers can declare an IME as "default" only for
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 9c4225d..39df5be 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 @@
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 @@
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 28a1c7a..85a1315 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 @@
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 @@
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 @@
@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 c110fb6..200b17b 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.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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
@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 @@
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 @@
// 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 @@
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 @@
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 @@
!= (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 @@
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 @@
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 @@
}
/**
- * Dependency injector for {@link #BackgroundInstallControlService)}.
+ * Dependency injector for {@link BackgroundInstallControlService}.
*/
interface Injector {
Context getContext();
@@ -532,6 +567,7 @@
Looper getLooper();
File getDiskFile();
+
}
private static final class InjectorImpl implements Injector {
@@ -568,11 +604,11 @@
@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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c5b5a76..609b3aa 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 @@
});
// 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 @@
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras, userIds, null /* instantUserIds */,
broadcastAllowList, mHandler, null /* filterExtras */);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c94111c..a6598d6 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 @@
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 @@
}
}
+ 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/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
index ed981e0..2154a26 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 @@
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/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
index 4fcdbfc..d479e52 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 7450607..c99e712 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 @@
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 @@
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 1fa1f84..cbe58a8 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 b74e561..b23f591 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.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 @@
@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/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
index 7cbfc52..570132f 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.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 @@
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/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 284e491..93a2eef 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.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.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.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 @@
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 @@
}
}
+ 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 @@
*/
@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 @@
@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 @@
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_NUM_FLEXIBLE_CONSTRAINTS,
- new int[] {10, 20, 30, 40});
+ 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_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 @@
@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 @@
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 @@
@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 @@
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 @@
@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 @@
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 @@
@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 @@
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 @@
@Test
public void testCalculateNumDroppedConstraints() {
- JobInfo.Builder jb = createJob(22);
- JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb);
- long nowElapsed = FROZEN_TIME;
+ 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");
- mFlexibilityController.mFlexibilityTracker.add(js);
+ 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;
- assertEquals(3, js.getNumRequiredFlexibleConstraints());
- assertEquals(0, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
+ 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);
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
- 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());
- assertEquals(1, mFlexibilityController
- .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
-
- nowElapsed = FROZEN_TIME;
- 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
+ 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 += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
+ nowElapsed = startElapsed;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
- assertEquals(0, js.getNumRequiredFlexibleConstraints());
- assertEquals(3, js.getNumDroppedFlexibleConstraints());
+ 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 = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
+ nowElapsed = startElapsed + 3 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
- assertEquals(1, js.getNumRequiredFlexibleConstraints());
- assertEquals(2, js.getNumDroppedFlexibleConstraints());
+ 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 = startElapsed + 4 * HOUR_IN_MILLIS;
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+ 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 @@
.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 @@
.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/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e6298ee..5bec903 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 @@
}
@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 9c8276a..84c0ab3 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.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.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 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 @@
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)));
-
- verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
- any());
- }
-
- @Test
- public void addCodecInfo_sendsInitialUpdateAfterStart() throws Exception {
- 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(mSessionId, /*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 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(newPiid, /*mediaCodecHash=*/222,
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(newSessionId, /*mediaCodecHash=*/222,
getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
}
@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),
- 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 {
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+ mLoudnessHelper.updateCodecParameters(
+ getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
- // no dispatch since mInitialApcPiid was not started
- verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ // no dispatch since mSessionId was not started
+ verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
+ any());
+ }
+
+ @Test
+ public void updateCodecParameters_dispatchUpdates() throws Exception {
+ final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true,
+ CODEC_METADATA_TYPE_MPEG_4);
+ mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info);
+
+ 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 @@
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 @@
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 @@
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();
+ assumeTrue(devIdx < devicesStatic.length);
+ Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + devIdx);
+ int deviceId = devicesStatic[devIdx].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);
+ 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);
- }
+ 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 a8eace0..c7300bb 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 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.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 @@
@Rule
public TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private IStatusBarService mStatusBarService;
@@ -395,6 +403,37 @@
}
}
+ @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 daf18ed..8656f60 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.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 @@
private Looper mLooper;
private File mFile;
-
@Mock
private Context mContext;
@Mock
@@ -108,8 +111,10 @@
private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Mock
private PermissionManagerServiceInternal mPermissionManager;
+
@Captor
private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
+
@Captor
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
@@ -119,11 +124,12 @@
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 @@
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
mBackgroundInstallControlService.initBackgroundInstalledPackages();
assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
- assertEquals(0,
- mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
+ assertEquals(0, mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
}
@Test
@@ -161,12 +166,9 @@
// 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 @@
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 @@
// 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 @@
!= (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 @@
// 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 @@
!= (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 @@
// 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 @@
!= (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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 @@
}
@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 @@
// 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 @@
}
@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 @@
// 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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
// 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 @@
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
assertNull(packages);
}
+
@Test
public void testPackageRemoved() {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -859,8 +895,7 @@
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 @@
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/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 94c737d..a089f5c 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -18,6 +18,7 @@
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.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 @@
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 @@
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 @@
return Objects.hash(mAccountHandle, mAddress, mSubscriptionAddress, mCapabilities,
mHighlightColor, mLabel, mShortDescription, mSupportedUriSchemes,
mSupportedAudioRoutes,
- mExtras, mIsEnabled, mGroupId);
+ mExtras, mIsEnabled, mGroupId, mSimultaneousCallingRestriction);
}
/**
@@ -537,6 +545,7 @@
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 @@
}
/**
+ * 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 @@
mExtras,
mSupportedAudioRoutes,
mIsEnabled,
- mGroupId);
+ mGroupId,
+ mSimultaneousCallingRestriction);
}
}
@@ -827,7 +888,8 @@
Bundle extras,
int supportedAudioRoutes,
boolean isEnabled,
- String groupId) {
+ String groupId,
+ Set<PhoneAccountHandle> simultaneousCallingRestriction) {
mAccountHandle = account;
mAddress = address;
mSubscriptionAddress = subscriptionAddress;
@@ -841,6 +903,7 @@
mSupportedAudioRoutes = supportedAudioRoutes;
mIsEnabled = isEnabled;
mGroupId = groupId;
+ mSimultaneousCallingRestriction = simultaneousCallingRestriction;
}
public static Builder builder(
@@ -1050,6 +1113,49 @@
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 @@
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 @@
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/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index acbf354..24296f4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3078,6 +3078,16 @@
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 2bc056e..cee27b6 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 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 @@
@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