Merge "Move IME unit tests to InputMethodSystemServerTests" 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 d2a895a7..0e413c4 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";
@@ -12318,6 +12319,7 @@
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+ method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -17875,6 +17877,7 @@
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
}
@@ -23317,6 +23320,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";
@@ -46952,6 +46957,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/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d8d136a..ea37e7f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1548,9 +1548,24 @@
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+ /**
+ * Whether the app has enabled to receive the icon overlay for fetching archived apps.
+ *
+ * @hide
+ */
+ public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+
+ /**
+ * Whether the app has enabled compatibility support for unarchival.
+ *
+ * @hide
+ */
+ public static final int OP_UNARCHIVAL_CONFIRMATION =
+ AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 144;
+ public static final int _NUM_OP = 146;
/**
* All app ops represented as strings.
@@ -1700,6 +1715,8 @@
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
OPSTR_RESERVED_FOR_TESTING,
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+ OPSTR_ARCHIVE_ICON_OVERLAY,
+ OPSTR_UNARCHIVAL_CONFIRMATION,
})
public @interface AppOpString {}
@@ -2040,6 +2057,20 @@
public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
/**
+ * Whether the app has enabled to receive the icon overlay for fetching archived apps.
+ *
+ * @hide
+ */
+ public static final String OPSTR_ARCHIVE_ICON_OVERLAY = "android:archive_icon_overlay";
+
+ /**
+ * Whether the app has enabled compatibility support for unarchival.
+ *
+ * @hide
+ */
+ public static final String OPSTR_UNARCHIVAL_CONFIRMATION = "android:unarchival_support";
+
+ /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -2504,6 +2535,8 @@
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OP_ARCHIVE_ICON_OVERLAY,
+ OP_UNARCHIVAL_CONFIRMATION,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2960,6 +2993,12 @@
OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
.setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
+ new AppOpInfo.Builder(OP_ARCHIVE_ICON_OVERLAY, OPSTR_ARCHIVE_ICON_OVERLAY,
+ "ARCHIVE_ICON_OVERLAY")
+ .setDefaultMode(MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION,
+ "UNARCHIVAL_CONFIRMATION")
+ .setDefaultMode(MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
@@ -3094,7 +3133,7 @@
/**
* Retrieve the permission associated with an operation, or null if there is not one.
- *
+
* @param op The operation name.
*
* @hide
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 34c44f9..4f1db7d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -4032,7 +4032,8 @@
private Drawable getArchivedAppIcon(String packageName) {
try {
return new BitmapDrawable(null,
- mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
+ mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()),
+ mContext.getPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index afa513d..c6712c0 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -413,7 +413,9 @@
* @hide
*/
public void setIntent(Intent startIntent) {
- mStartIntent = startIntent;
+ if (startIntent != null) {
+ mStartIntent = startIntent.maybeStripForHistory();
+ }
}
/**
@@ -548,6 +550,8 @@
/**
* The intent used to launch the application.
*
+ * <p class="note"> Note: Intent is stripped and does not include extras.</p>
+ *
* <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
*/
@SuppressLint("IntentBuilderName")
@@ -662,6 +666,7 @@
private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp";
private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key";
private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_INTENT = "intent";
/**
* Write to a protocol buffer output stream. Protocol buffer message definition at {@link
@@ -702,10 +707,17 @@
}
proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
if (mStartIntent != null) {
- Parcel parcel = Parcel.obtain();
- mStartIntent.writeToParcel(parcel, 0);
- proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall());
- parcel.recycle();
+ ByteArrayOutputStream intentBytes = new ByteArrayOutputStream();
+ ObjectOutputStream intentOut = new ObjectOutputStream(intentBytes);
+ TypedXmlSerializer serializer = Xml.resolveSerializer(intentOut);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ mStartIntent.saveToXml(serializer);
+ serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ serializer.endDocument();
+ proto.write(ApplicationStartInfoProto.START_INTENT,
+ intentBytes.toByteArray());
+ intentOut.close();
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
proto.end(token);
@@ -772,15 +784,17 @@
mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
break;
case (int) ApplicationStartInfoProto.START_INTENT:
- byte[] startIntentBytes = proto.readBytes(
- ApplicationStartInfoProto.START_INTENT);
- if (startIntentBytes.length > 0) {
- Parcel parcel = Parcel.obtain();
- parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length);
- parcel.setDataPosition(0);
- mStartIntent = Intent.CREATOR.createFromParcel(parcel);
- parcel.recycle();
+ ByteArrayInputStream intentBytes = new ByteArrayInputStream(proto.readBytes(
+ ApplicationStartInfoProto.START_INTENT));
+ ObjectInputStream intentIn = new ObjectInputStream(intentBytes);
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(intentIn);
+ XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ mStartIntent = Intent.restoreFromXml(parser);
+ } catch (XmlPullParserException e) {
+ // Intent lost
}
+ intentIn.close();
break;
case (int) ApplicationStartInfoProto.LAUNCH_MODE:
mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE);
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 1ac08ac..0261f0a 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -179,14 +179,6 @@
@Overridable
public static final long BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT = 236704164L;
- /**
- * Validate options passed in as bundle.
- * @hide
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public static final long PENDING_INTENT_OPTIONS_CHECK = 320664730L;
-
/** @hide */
@IntDef(flag = true,
value = {
diff --git a/core/java/android/content/pm/ArchivedActivityInfo.java b/core/java/android/content/pm/ArchivedActivityInfo.java
index 1faa437..166d265 100644
--- a/core/java/android/content/pm/ArchivedActivityInfo.java
+++ b/core/java/android/content/pm/ArchivedActivityInfo.java
@@ -91,26 +91,31 @@
* @hide
*/
public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) {
- if (drawable instanceof BitmapDrawable) {
- return ((BitmapDrawable) drawable).getBitmap();
-
- }
-
Bitmap bitmap;
- if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
- // Needed for drawables that are just a single color.
- bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ if (drawable instanceof BitmapDrawable) {
+ bitmap = ((BitmapDrawable) drawable).getBitmap();
} else {
- bitmap =
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ // Needed for drawables that are just a single color.
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ } else {
+ bitmap =
Bitmap.createBitmap(
- drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
}
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
- if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
+ if (iconSize <= 0) {
+ return bitmap;
+ }
+
+ if (bitmap.getWidth() < iconSize || bitmap.getHeight() < iconSize
+ || bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
if (scaledBitmap != bitmap) {
bitmap.recycle();
@@ -235,7 +240,7 @@
}
@DataClass.Generated(
- time = 1698789991876L,
+ time = 1705615445673L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivityInfo.java",
inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)")
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index a97de63..62db65f 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -128,4 +128,6 @@
/** Unregister a callback, so that it won't be called when LauncherApps dumps. */
void unRegisterDumpCallback(IDumpCallback cb);
+
+ void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6dc8d47..380de96 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -840,7 +840,7 @@
ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
- Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
+ Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName);
boolean isAppArchivable(String packageName, in UserHandle user);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 1d2b1af..50be983 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1801,6 +1801,31 @@
}
}
+ /**
+ * Enable or disable different archive compatibility options of the launcher.
+ *
+ * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
+ * that a certain app is archived. True by default.
+ * Launchers might want to disable this operation if they want to provide custom user experience
+ * to differentiate archived apps.
+ * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
+ * they click an archived app, which explains that the app will be downloaded and restored in
+ * the background. True by default.
+ * Launchers might want to disable this operation if they provide sufficient, alternative user
+ * guidance to highlight that an unarchival is starting and ongoing once an archived app is
+ * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+ public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
+ boolean enableUnarchivalConfirmation) {
+ try {
+ mService.setArchiveCompatibilityOptions(enableIconOverlay,
+ enableUnarchivalConfirmation);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
/** @return position in mCallbacks for callback or -1 if not present. */
private int findCallbackLocked(Callback callback) {
if (callback == null) {
diff --git a/core/java/android/content/pm/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/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8a4f678..35ae3c9 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -40,12 +40,15 @@
import android.os.OperationCanceledException;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Printer;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.NeverCompile;
@@ -103,8 +106,14 @@
// Stores reference to all databases opened in the current process.
// (The referent Object is not used at this time.)
// INVARIANT: Guarded by sActiveDatabases.
+ @GuardedBy("sActiveDatabases")
private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>();
+ // Tracks which database files are currently open. If a database file is opened more than
+ // once at any given moment, the associated databases are marked as "concurrent".
+ @GuardedBy("sActiveDatabases")
+ private static final OpenTracker sOpenTracker = new OpenTracker();
+
// Thread-local for database sessions that belong to this database.
// Each thread has its own database session.
// INVARIANT: Immutable.
@@ -510,6 +519,7 @@
private void dispose(boolean finalized) {
final SQLiteConnectionPool pool;
+ final String path;
synchronized (mLock) {
if (mCloseGuardLocked != null) {
if (finalized) {
@@ -520,10 +530,12 @@
pool = mConnectionPoolLocked;
mConnectionPoolLocked = null;
+ path = isInMemoryDatabase() ? null : getPath();
}
if (!finalized) {
synchronized (sActiveDatabases) {
+ sOpenTracker.close(path);
sActiveDatabases.remove(this);
}
@@ -1132,6 +1144,74 @@
}
}
+ /**
+ * Track the number of times a database file has been opened. There is a primary connection
+ * associated with every open database, and these can contend with each other, leading to
+ * unexpected SQLiteDatabaseLockedException exceptions. The tracking here is only advisory:
+ * multiply-opened databases are logged but no other action is taken.
+ *
+ * This class is not thread-safe.
+ */
+ private static class OpenTracker {
+ // The list of currently-open databases. This maps the database file to the number of
+ // currently-active opens.
+ private final ArrayMap<String, Integer> mOpens = new ArrayMap<>();
+
+ // The maximum number of concurrently open database paths that will be stored. Once this
+ // many paths have been recorded, further paths are logged but not saved.
+ private static final int MAX_RECORDED_PATHS = 20;
+
+ // The list of databases that were ever concurrently opened.
+ private final ArraySet<String> mConcurrent = new ArraySet<>();
+
+ /** Return the canonical path. On error, just return the input path. */
+ private static String normalize(String path) {
+ try {
+ return new File(path).toPath().toRealPath().toString();
+ } catch (Exception e) {
+ // If there is an IO or security exception, just continue, using the input path.
+ return path;
+ }
+ }
+
+ /** Return true if the path is currently open in another SQLiteDatabase instance. */
+ void open(@Nullable String path) {
+ if (path == null) return;
+ path = normalize(path);
+
+ Integer count = mOpens.get(path);
+ if (count == null || count == 0) {
+ mOpens.put(path, 1);
+ return;
+ } else {
+ mOpens.put(path, count + 1);
+ if (mConcurrent.size() < MAX_RECORDED_PATHS) {
+ mConcurrent.add(path);
+ }
+ Log.w(TAG, "multiple primary connections on " + path);
+ return;
+ }
+ }
+
+ void close(@Nullable String path) {
+ if (path == null) return;
+ path = normalize(path);
+ Integer count = mOpens.get(path);
+ if (count == null || count <= 0) {
+ Log.e(TAG, "open database counting failure on " + path);
+ } else if (count == 1) {
+ // Implicitly set the count to zero, and make mOpens smaller.
+ mOpens.remove(path);
+ } else {
+ mOpens.put(path, count - 1);
+ }
+ }
+
+ ArraySet<String> getConcurrentDatabasePaths() {
+ return new ArraySet<>(mConcurrent);
+ }
+ }
+
private void open() {
try {
try {
@@ -1153,14 +1233,17 @@
}
private void openInner() {
+ final String path;
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
+ path = isInMemoryDatabase() ? null : getPath();
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
+ sOpenTracker.open(path);
}
}
@@ -2345,6 +2428,17 @@
}
/**
+ * Return list of databases that have been concurrently opened.
+ * @hide
+ */
+ @VisibleForTesting
+ public static ArraySet<String> getConcurrentDatabasePaths() {
+ synchronized (sActiveDatabases) {
+ return sOpenTracker.getConcurrentDatabasePaths();
+ }
+ }
+
+ /**
* Returns true if the new version code is greater than the current database version.
*
* @param newVersion The new version code.
@@ -2766,6 +2860,19 @@
dumpDatabaseDirectory(printer, new File(dir), isSystem);
}
}
+
+ // Dump concurrently-opened database files, if any
+ final ArraySet<String> concurrent;
+ synchronized (sActiveDatabases) {
+ concurrent = sOpenTracker.getConcurrentDatabasePaths();
+ }
+ if (concurrent.size() > 0) {
+ printer.println("");
+ printer.println("Concurrently opened database files");
+ for (String f : concurrent) {
+ printer.println(" " + f);
+ }
+ }
}
private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 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/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 e9d69b4..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.
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index e118c98d..3ee565f 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -403,4 +403,41 @@
}
assertFalse(allowed);
}
+
+ /** Return true if the path is in the list of strings. */
+ private boolean isConcurrent(String path) throws Exception {
+ path = new File(path).toPath().toRealPath().toString();
+ return SQLiteDatabase.getConcurrentDatabasePaths().contains(path);
+ }
+
+ @Test
+ public void testDuplicateDatabases() throws Exception {
+ // The two database paths in this test are assumed not to have been opened earlier in this
+ // process.
+
+ // A database path that will be opened twice.
+ final String dbName = "never-used-db.db";
+ final File dbFile = mContext.getDatabasePath(dbName);
+ final String dbPath = dbFile.getPath();
+
+ // A database path that will be opened only once.
+ final String okName = "never-used-ok.db";
+ final File okFile = mContext.getDatabasePath(okName);
+ final String okPath = okFile.getPath();
+
+ SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+ assertFalse(isConcurrent(dbPath));
+ SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+ assertTrue(isConcurrent(dbPath));
+ db1.close();
+ assertTrue(isConcurrent(dbPath));
+ db2.close();
+ assertTrue(isConcurrent(dbPath));
+
+ SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
+ db3.close();
+ db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
+ assertFalse(isConcurrent(okPath));
+ db3.close();
+ }
}
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
new file mode 100644
index 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/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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d4a9289..a5f7880 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -137,7 +137,7 @@
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
public class BubbleController implements ConfigurationChangeListener,
- RemoteCallable<BubbleController> {
+ RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -706,6 +706,7 @@
return mBubbleIconFactory;
}
+ @Override
public Bubbles.SysuiProxy getSysuiProxy() {
return mSysuiProxy;
}
@@ -732,8 +733,7 @@
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator,
- mMainExecutor);
+ mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c25d412..a619401 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -206,6 +206,7 @@
};
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
+ private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
private final ValueAnimator mDismissBubbleAnimator;
@@ -875,12 +876,14 @@
public BubbleStackView(Context context, BubbleController bubbleController,
BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
+ Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
mBubbleController = bubbleController;
mBubbleData = data;
+ mSysuiProxyProvider = sysuiProxyProvider;
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
@@ -2090,7 +2093,7 @@
hideCurrentInputMethod();
- mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
+ mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);
if (wasExpanded) {
stopMonitoringSwipeUpGesture();
@@ -3034,7 +3037,7 @@
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
mManageMenuScrim.setVisibility(INVISIBLE);
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
return;
}
if (show) {
@@ -3048,7 +3051,7 @@
}
};
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
.alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 759246e..28af0ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -321,6 +321,13 @@
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
+
+ /** Provider interface for {@link SysuiProxy}. */
+ interface Provider {
+ /** Returns {@link SysuiProxy}. */
+ SysuiProxy getSysuiProxy();
+ }
+
void isNotificationPanelExpand(Consumer<Boolean> callback);
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index f583321..4878df8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -125,7 +125,7 @@
mock<BubbleProperties>())
bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
- surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor)
+ surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
}
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/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/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 17f2525..687feef 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -3109,9 +3109,8 @@
mStub, mDiscoveryPreference);
}
- if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
- unregisterRouterStubLocked();
- }
+ unregisterRouterStubIfNeededLocked();
+
} catch (RemoteException ex) {
Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
}
@@ -3319,13 +3318,12 @@
obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller));
}
- if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
- try {
- unregisterRouterStubLocked();
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
- }
+ try {
+ unregisterRouterStubIfNeededLocked();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
}
+
}
}
@@ -3339,8 +3337,10 @@
}
@GuardedBy("mLock")
- private void unregisterRouterStubLocked() throws RemoteException {
- if (mStub != null) {
+ private void unregisterRouterStubIfNeededLocked() throws RemoteException {
+ if (mStub != null
+ && mRouteCallbackRecords.isEmpty()
+ && mNonSystemRoutingControllers.isEmpty()) {
mMediaRouterService.unregisterRouter2(mStub);
mStub = null;
}
diff --git a/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/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index d6f1eab..15f33d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -229,6 +229,17 @@
@SuppressWarnings("NewApi")
@Override
public String getId() {
+ if (com.android.media.flags.Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+ // Note: be careful when removing this flag. Instead of just removing it, you might want
+ // to replace it with SDK_INT >= 35. Explanation: The presence of SDK checks in settings
+ // lib suggests that a mainline component may depend on this code. Which means removing
+ // this "if" (and using always the route info id) could mean a regression on mainline
+ // code running on a device that's running API 34 or older. Unfortunately, we cannot
+ // check the API level at the moment of writing this code because the API level has not
+ // been bumped, yet.
+ return mRouteInfo.getId();
+ }
+
String id;
switch (mRouteInfo.getType()) {
case TYPE_WIRED_HEADSET:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 1746bef..ceba9be 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -31,10 +31,15 @@
import android.content.Context;
import android.media.MediaRoute2Info;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import com.android.media.flags.Flags;
import com.android.settingslib.R;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +50,8 @@
@RunWith(RobolectricTestRunner.class)
public class PhoneMediaDeviceTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private MediaRoute2Info mInfo;
@@ -110,8 +117,18 @@
.isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
@Test
- public void getId_returnCorrectId() {
+ public void getId_whenAdvancedWiredRoutingEnabled_returnCorrectId() {
+ String fakeId = "foo";
+ when(mInfo.getId()).thenReturn(fakeId);
+
+ assertThat(mPhoneMediaDevice.getId()).isEqualTo(fakeId);
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+ @Test
+ public void getId_whenAdvancedWiredRoutingDisabled_returnCorrectId() {
when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
assertThat(mPhoneMediaDevice.getId())
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2d442f4..3a46f4e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -406,7 +406,7 @@
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
- mSettingsRegistry = new SettingsRegistry();
+ mSettingsRegistry = new SettingsRegistry(mHandlerThread.getLooper());
}
SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext());
synchronized (mLock) {
@@ -2896,8 +2896,8 @@
private String mSettingsCreationBuildId;
- public SettingsRegistry() {
- mHandler = new MyHandler(getContext().getMainLooper());
+ SettingsRegistry(Looper looper) {
+ mHandler = new MyHandler(looper);
mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers());
mBackupManager = new BackupManager(getContext());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 867a48d..a2dec5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -24,19 +24,21 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -59,8 +61,6 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var communalRepository: FakeCommunalRepository
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
@@ -72,17 +72,14 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- val withDeps = CommunalInteractorFactory.create(testScope)
- keyguardRepository = withDeps.keyguardRepository
- communalRepository = withDeps.communalRepository
- tutorialRepository = withDeps.tutorialRepository
- widgetRepository = withDeps.widgetRepository
- smartspaceRepository = withDeps.smartspaceRepository
- mediaRepository = withDeps.mediaRepository
+ tutorialRepository = kosmos.fakeCommunalTutorialRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ smartspaceRepository = kosmos.fakeSmartspaceRepository
+ mediaRepository = kosmos.fakeCommunalMediaRepository
underTest =
CommunalEditModeViewModel(
- withDeps.communalInteractor,
+ kosmos.communalInteractor,
mediaHost,
uiEventLogger,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9ac21dd..033dc6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -23,25 +23,30 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -58,15 +63,14 @@
class CommunalViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var communalRepository: FakeCommunalRepository
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
- private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var underTest: CommunalViewModel
@@ -74,22 +78,17 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
-
- val withDeps = CommunalInteractorFactory.create()
- keyguardRepository = withDeps.keyguardRepository
- communalRepository = withDeps.communalRepository
- tutorialRepository = withDeps.tutorialRepository
- widgetRepository = withDeps.widgetRepository
- smartspaceRepository = withDeps.smartspaceRepository
- mediaRepository = withDeps.mediaRepository
- communalPrefsRepository = withDeps.communalPrefsRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ tutorialRepository = kosmos.fakeCommunalTutorialRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ smartspaceRepository = kosmos.fakeSmartspaceRepository
+ mediaRepository = kosmos.fakeCommunalMediaRepository
underTest =
CommunalViewModel(
testScope,
- withDeps.communalInteractor,
- withDeps.tutorialInteractor,
+ kosmos.communalInteractor,
+ kosmos.communalTutorialInteractor,
mediaHost,
)
}
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/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/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 2bb9d0e..a909383 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -74,6 +74,7 @@
// before the MediaHierarchyManager attempts to move the UMO to the hub.
with(mediaHost) {
expansion = MediaHostState.EXPANDED
+ expandedMatchesParentHeight = true
showsOnlyActiveMedia = false
falsingProtectionNeeded = false
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index 631a0b8..437218f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -228,6 +228,14 @@
}
}
+ override var expandedMatchesParentHeight: Boolean = false
+ set(value) {
+ if (value != field) {
+ field = value
+ changedListener?.invoke()
+ }
+ }
+
override var squishFraction: Float = 1.0f
set(value) {
if (!value.equals(field)) {
@@ -282,6 +290,7 @@
override fun copy(): MediaHostState {
val mediaHostState = MediaHostStateHolder()
mediaHostState.expansion = expansion
+ mediaHostState.expandedMatchesParentHeight = expandedMatchesParentHeight
mediaHostState.squishFraction = squishFraction
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
mediaHostState.measurementInput = measurementInput?.copy()
@@ -360,6 +369,12 @@
*/
var expansion: Float
+ /**
+ * If true, the [EXPANDED] layout should stretch to match the height of its parent container,
+ * rather than having a fixed height.
+ */
+ var expandedMatchesParentHeight: Boolean
+
/** Fraction of the height animation. */
var squishFraction: Float
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index be93936..962764c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -20,6 +20,7 @@
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import com.android.app.tracing.traceSection
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
@@ -152,18 +153,11 @@
lastOrientation = newOrientation
// Update the height of media controls for the expanded layout. it is needed
// for large screen devices.
- val backgroundIds =
- if (type == TYPE.PLAYER) {
- MediaViewHolder.backgroundIds
- } else {
- setOf(RecommendationViewHolder.backgroundId)
- }
- backgroundIds.forEach { id ->
- expandedLayout.getConstraint(id).layout.mHeight =
- context.resources.getDimensionPixelSize(
- R.dimen.qs_media_session_height_expanded
- )
- }
+ setBackgroundHeights(
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ )
}
if (this@MediaViewController::configurationChangeListener.isInitialized) {
configurationChangeListener.invoke()
@@ -276,6 +270,17 @@
private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
if (expansion > 0) expandedLayout else collapsedLayout
+ /** Set the height of UMO background constraints. */
+ private fun setBackgroundHeights(height: Int) {
+ val backgroundIds =
+ if (type == TYPE.PLAYER) {
+ MediaViewHolder.backgroundIds
+ } else {
+ setOf(RecommendationViewHolder.backgroundId)
+ }
+ backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height }
+ }
+
/**
* Set the views to be showing/hidden based on the [isGutsVisible] for a given
* [TransitionViewState].
@@ -454,6 +459,18 @@
}
// Let's create a new measurement
if (state.expansion == 0.0f || state.expansion == 1.0f) {
+ if (state.expansion == 1.0f) {
+ val height =
+ if (state.expandedMatchesParentHeight) {
+ MATCH_CONSTRAINT
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
+ setBackgroundHeights(height)
+ }
+
result =
transitionLayout!!.calculateViewState(
state.measurementInput!!,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 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/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 4565200..c5eeb2f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -249,6 +249,10 @@
height = iconSize
marginEnd = endMargin
}
+
+ background = createTileBackground()
+ setColor(backgroundColor)
+ setOverlayColor(backgroundOverlayColor)
}
private fun createAndAddLabels() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 0bc8e68..f375ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -187,7 +187,7 @@
configuration.getDimensionPixelSize(RInternal.dimen.status_bar_icon_size_sp)
val iconHorizontalPaddingFlow: Flow<Int> =
configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
- val layoutParams: Flow<FrameLayout.LayoutParams> =
+ val layoutParams: StateFlow<FrameLayout.LayoutParams> =
combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) {
iconSize,
iconHPadding,
@@ -206,7 +206,7 @@
private suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
- layoutParams: Flow<FrameLayout.LayoutParams>,
+ layoutParams: StateFlow<FrameLayout.LayoutParams>,
notifyBindingFailures: (Collection<String>) -> Unit,
viewStore: IconViewStore,
bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit,
@@ -259,7 +259,7 @@
// added again.
removeTransientView(sbiv)
}
- view.addView(sbiv)
+ view.addView(sbiv, layoutParams.value)
boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
boundViewsByNotifKey[notifKey] =
Pair(
@@ -267,7 +267,9 @@
launch {
launch {
layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
- sbiv.layoutParams = it
+ if (it != sbiv.layoutParams) {
+ sbiv.layoutParams = it
+ }
}
}
bindIcon(notifKey, sbiv)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
index ce811e2..10137a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
@@ -17,10 +17,16 @@
package com.android.systemui.statusbar.ui
import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -31,6 +37,8 @@
class SystemBarUtilsState
@Inject
constructor(
+ @Background bgContext: CoroutineContext,
+ @Main mainContext: CoroutineContext,
configurationController: ConfigurationController,
proxy: SystemBarUtilsProxy,
) {
@@ -38,5 +46,10 @@
val statusBarHeight: Flow<Int> =
configurationController.onConfigChanged
.onStart<Any> { emit(Unit) }
+ .flowOn(mainContext)
+ .conflate()
.map { proxy.getStatusBarHeight() }
+ .distinctUntilChanged()
+ .flowOn(bgContext)
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 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/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 90eaa5a..dafd9e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,8 +23,9 @@
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
@@ -34,6 +35,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -42,11 +45,15 @@
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -57,7 +64,6 @@
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -83,7 +89,8 @@
@SmallTest
@RunWith(JUnit4::class)
class KeyguardTransitionScenariosTest : SysuiTestCase() {
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
@@ -119,17 +126,14 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- val testDispatcher = StandardTestDispatcher()
- testScope = TestScope(testDispatcher)
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ bouncerRepository = kosmos.fakeKeyguardBouncerRepository
commandQueue = FakeCommandQueue()
- shadeRepository = FakeShadeRepository()
- transitionRepository = spy(FakeKeyguardTransitionRepository())
+ shadeRepository = kosmos.fakeShadeRepository
+ transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository)
powerInteractor = PowerInteractorFactory.create().powerInteractor
- communalInteractor =
- CommunalInteractorFactory.create(testScope = testScope).communalInteractor
+ communalInteractor = kosmos.communalInteractor
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
@@ -160,8 +164,8 @@
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -184,8 +188,8 @@
fromPrimaryBouncerTransitionInteractor =
FromPrimaryBouncerTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -199,8 +203,8 @@
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -210,8 +214,8 @@
fromDreamingLockscreenHostedTransitionInteractor =
FromDreamingLockscreenHostedTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -221,8 +225,8 @@
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -232,8 +236,8 @@
fromGoneTransitionInteractor =
FromGoneTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -244,8 +248,8 @@
fromDozingTransitionInteractor =
FromDozingTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -257,8 +261,8 @@
fromOccludedTransitionInteractor =
FromOccludedTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -269,8 +273,8 @@
fromAlternateBouncerTransitionInteractor =
FromAlternateBouncerTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -281,8 +285,8 @@
fromGlanceableHubTransitionInteractor =
FromGlanceableHubTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
glanceableHubTransitions = glanceableHubTransitions,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 6be9275..ba7927d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -25,8 +25,7 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
@@ -43,6 +42,7 @@
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -79,6 +79,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class MediaHierarchyManagerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock private lateinit var lockHost: MediaHost
@Mock private lateinit var qsHost: MediaHost
@Mock private lateinit var qqsHost: MediaHost
@@ -110,10 +112,7 @@
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val communalRepository =
- FakeCommunalRepository(applicationScope = testScope.backgroundScope)
- private val communalInteractor =
- CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
+ private val communalInteractor = kosmos.communalInteractor
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 0a464e6..b701d7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -21,6 +21,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.models.player.MediaViewHolder
@@ -171,6 +172,38 @@
}
@Test
+ fun testObtainViewState_expandedMatchesParentHeight() {
+ mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ player.measureState =
+ TransitionViewState().apply {
+ this.height = 100
+ this.measureHeight = 100
+ }
+ mediaHostStateHolder.expandedMatchesParentHeight = true
+ mediaHostStateHolder.expansion = 1f
+ mediaHostStateHolder.measurementInput =
+ MeasurementInput(
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
+ )
+
+ // Assign the height of each expanded layout
+ MediaViewHolder.backgroundIds.forEach { id ->
+ mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 100
+ }
+
+ mediaViewController.obtainViewState(mediaHostStateHolder)
+
+ // Verify height of each expanded layout is updated to match constraint
+ MediaViewHolder.backgroundIds.forEach { id ->
+ assertTrue(
+ mediaViewController.expandedLayout.getConstraint(id).layout.mHeight ==
+ ConstraintSet.MATCH_CONSTRAINT
+ )
+ }
+ }
+
+ @Test
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() {
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
whenever(mockCopiedState.widgetStates)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 72847a6..c6cfabc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -414,6 +414,18 @@
@Test
public void onDeviceListUpdate_verifyDeviceListCallback() {
+ // This test relies on mMediaOutputController.start being called while the selected device
+ // list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
mMediaOutputController.start(mCb);
reset(mCb);
@@ -434,6 +446,18 @@
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
+ // This test relies on mMediaOutputController.start being called while the selected device
+ // list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
when(mMediaDevice1.getFeatures()).thenReturn(
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index b7a9ea7..81ff817 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -25,14 +25,16 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -52,6 +54,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class GlanceableHubContainerControllerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@@ -71,9 +75,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- val withDeps = CommunalInteractorFactory.create()
- communalInteractor = withDeps.communalInteractor
- communalRepository = withDeps.communalRepository
+ communalInteractor = kosmos.communalInteractor
+ communalRepository = kosmos.fakeCommunalRepository
underTest =
GlanceableHubContainerController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 11da237..0bffa1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -57,7 +57,6 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -202,8 +201,7 @@
new ConfigurationInteractor(configurationRepository),
shadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor =
- CommunalInteractorFactory.create().getCommunalInteractor();
+ CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 8f46a37..6fc88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -43,7 +43,6 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -233,8 +232,7 @@
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor =
- CommunalInteractorFactory.create().getCommunalInteractor();
+ CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 05e866e..13934da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,7 +27,7 @@
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -144,7 +144,7 @@
{ fromLockscreenTransitionInteractor },
{ fromPrimaryBouncerTransitionInteractor }
)
- val communalInteractor = CommunalInteractorFactory.create().communalInteractor
+ val communalInteractor = kosmos.communalInteractor
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 589f7c2..744f424 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -100,7 +100,6 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -443,8 +442,7 @@
() -> keyguardInteractor,
() -> mFromLockscreenTransitionInteractor,
() -> mFromPrimaryBouncerTransitionInteractor);
- CommunalInteractor communalInteractor =
- CommunalInteractorFactory.create().getCommunalInteractor();
+ CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
deleted file mode 100644
index 1ba8630..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.communal.domain.interactor
-
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.util.mockito.mock
-import kotlinx.coroutines.test.TestScope
-
-// TODO(b/319335645): get rid of me and use kosmos.
-object CommunalInteractorFactory {
-
- @JvmOverloads
- @JvmStatic
- fun create(
- testScope: TestScope = TestScope(),
- communalRepository: FakeCommunalRepository =
- FakeCommunalRepository(testScope.backgroundScope),
- keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
- widgetRepository: FakeCommunalWidgetRepository =
- FakeCommunalWidgetRepository(testScope.backgroundScope),
- mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
- smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
- tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
- communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
- appWidgetHost: CommunalAppWidgetHost = mock(),
- editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
- ): WithDependencies {
- val keyguardInteractor =
- KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
- val communalTutorialInteractor =
- CommunalTutorialInteractor(
- testScope.backgroundScope,
- tutorialRepository,
- keyguardInteractor,
- communalRepository,
- )
-
- return WithDependencies(
- testScope,
- communalRepository,
- widgetRepository,
- communalPrefsRepository,
- mediaRepository,
- smartspaceRepository,
- tutorialRepository,
- keyguardRepository,
- keyguardInteractor,
- communalTutorialInteractor,
- appWidgetHost,
- editWidgetsActivityStarter,
- CommunalInteractor(
- testScope.backgroundScope,
- communalRepository,
- widgetRepository,
- communalPrefsRepository,
- mediaRepository,
- smartspaceRepository,
- keyguardInteractor,
- appWidgetHost,
- editWidgetsActivityStarter,
- ),
- )
- }
-
- data class WithDependencies(
- val testScope: TestScope,
- val communalRepository: FakeCommunalRepository,
- val widgetRepository: FakeCommunalWidgetRepository,
- val communalPrefsRepository: FakeCommunalPrefsRepository,
- val mediaRepository: FakeCommunalMediaRepository,
- val smartspaceRepository: FakeSmartspaceRepository,
- val tutorialRepository: FakeCommunalTutorialRepository,
- val keyguardRepository: FakeKeyguardRepository,
- val keyguardInteractor: KeyguardInteractor,
- val tutorialInteractor: CommunalTutorialInteractor,
- val appWidgetHost: CommunalAppWidgetHost,
- val editWidgetsActivityStarter: EditWidgetsActivityStarter,
- val communalInteractor: CommunalInteractor,
- )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index cc0449d..321f944 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -26,6 +26,7 @@
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -72,6 +73,7 @@
val powerInteractor by lazy { kosmos.powerInteractor }
val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+ val communalInteractor by lazy { kosmos.communalInteractor }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
index e208add..5476d55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
@@ -17,7 +17,15 @@
package com.android.systemui.statusbar.ui
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.policy.configurationController
-val Kosmos.systemBarUtilsState by
- Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) }
+val Kosmos.systemBarUtilsState by Fixture {
+ SystemBarUtilsState(
+ testDispatcher,
+ testDispatcher,
+ configurationController,
+ systemBarUtilsProxy,
+ )
+}
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index 5df9107..a20623c 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -30,7 +30,6 @@
import android.app.AppGlobals;
import android.app.PendingIntent;
import android.app.PendingIntentStats;
-import android.app.compat.CompatChanges;
import android.content.IIntentSender;
import android.content.Intent;
import android.os.Binder;
@@ -137,11 +136,6 @@
+ "intent creator ("
+ packageName
+ ") because this option is meant for the pending intent sender");
- if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK,
- callingUid)) {
- throw new IllegalArgumentException("pendingIntentBackgroundActivityStartMode "
- + "must not be set when creating a PendingIntent");
- }
opts.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 95e130e..10d5fd3 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -406,9 +406,6 @@
String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver,
String requiredPermission, IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options) {
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
-
if (intent != null) intent.setDefusable(true);
if (options != null) options.setDefusable(true);
@@ -461,12 +458,6 @@
+ key.packageName
+ ") because this option is meant for the pending intent "
+ "creator");
- if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK,
- callingUid)) {
- throw new IllegalArgumentException(
- "pendingIntentCreatorBackgroundActivityStartMode "
- + "must not be set when sending a PendingIntent");
- }
opts.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
}
@@ -503,6 +494,9 @@
}
// We don't hold the controller lock beyond this point as we will be calling into AM and WM.
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+
// Only system senders can declare a broadcast to be alarm-originated. We check
// this here rather than in the general case handling below to fail before the other
// invocation side effects such as allowlisting.
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/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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ac826af..68c95b1 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,6 +18,10 @@
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY;
+import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -43,6 +47,7 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyCache;
@@ -213,6 +218,7 @@
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageManagerInternal mPackageManagerInternal;
+ private final AppOpsManager mAppOpsManager;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private final DevicePolicyManager mDpm;
@@ -253,6 +259,7 @@
LocalServices.getService(ShortcutServiceInternal.class));
mPackageManagerInternal = Objects.requireNonNull(
LocalServices.getService(PackageManagerInternal.class));
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mShortcutServiceInternal.addListener(mPackageMonitor);
mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
@@ -1998,6 +2005,23 @@
}
}
+ @Override
+ public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
+ boolean enableUnarchivalConfirmation) {
+ int callingUid = Binder.getCallingUid();
+ Binder.withCleanCallingIdentity(
+ () -> {
+ mAppOpsManager.setUidMode(
+ OP_ARCHIVE_ICON_OVERLAY,
+ callingUid,
+ enableIconOverlay ? MODE_ALLOWED : MODE_IGNORED);
+ mAppOpsManager.setUidMode(
+ OP_UNARCHIVAL_CONFIRMATION,
+ callingUid,
+ enableUnarchivalConfirmation ? MODE_ALLOWED : MODE_IGNORED);
+ });
+ }
+
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 09a91ed..c1b3673 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
@@ -31,6 +32,7 @@
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
+import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -66,8 +68,11 @@
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Binder;
import android.os.Bundle;
@@ -269,11 +274,12 @@
Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
try {
- // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
- false /* showUnarchivalConfirmation= */);
+ getAppOpsManager().checkOp(
+ AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
+ == MODE_ALLOWED);
} catch (Throwable t) {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
@@ -379,9 +385,8 @@
verifyNotSystemApp(ps.getFlags());
verifyInstalled(ps, userId);
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
- verifyInstaller(responsibleInstallerPackage, userId);
- ApplicationInfo installerInfo = snapshot.getApplicationInfo(
- responsibleInstallerPackage, /* flags= */ 0, userId);
+ ApplicationInfo installerInfo = verifyInstaller(
+ snapshot, responsibleInstallerPackage, userId);
verifyOptOutStatus(packageName,
UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId())));
@@ -421,10 +426,10 @@
List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
for (int i = 0, size = mainActivities.size(); i < size; ++i) {
var mainActivity = mainActivities.get(i);
- Path iconPath = storeDrawable(
- packageName, mainActivity.getIcon(), userId, i, iconSize);
- Path monochromePath = storeDrawable(
- packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize);
+ Path iconPath = storeAdaptiveDrawable(
+ packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize);
+ Path monochromePath = storeAdaptiveDrawable(
+ packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize);
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
mainActivity.getLabel().toString(),
@@ -451,7 +456,8 @@
List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
- Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize);
+ Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize);
+ // i * 2 + 1 reserved for monochromeIcon
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
mainActivity.getLabel().toString(),
@@ -495,8 +501,30 @@
return iconFile.toPath();
}
- private void verifyInstaller(String installerPackageName, int userId)
- throws PackageManager.NameNotFoundException {
+ /**
+ * Create an <a
+ * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive">
+ * adaptive icon</a> from an icon.
+ * This is necessary so the icon can be displayed properly by different launchers.
+ */
+ private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable,
+ @UserIdInt int userId, int index, int iconSize) throws IOException {
+ if (iconDrawable == null) {
+ return null;
+ }
+
+ // see BaseIconFactory#createShapedIconBitmap
+ float inset = getExtraInsetFraction();
+ inset = inset / (1 + 2 * inset);
+ Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
+ new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset));
+
+ return storeDrawable(packageName, d, userId, index, iconSize);
+ }
+
+
+ private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName,
+ int userId) throws PackageManager.NameNotFoundException {
if (TextUtils.isEmpty(installerPackageName)) {
throw new PackageManager.NameNotFoundException("No installer found");
}
@@ -505,6 +533,12 @@
&& !verifySupportsUnarchival(installerPackageName, userId)) {
throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
}
+ ApplicationInfo appInfo = snapshot.getApplicationInfo(
+ installerPackageName, /* flags=*/ 0, userId);
+ if (appInfo == null) {
+ throw new PackageManager.NameNotFoundException("Failed to obtain Installer info");
+ }
+ return appInfo;
}
/**
@@ -570,7 +604,7 @@
}
try {
- verifyInstaller(getResponsibleInstallerPackage(ps), userId);
+ verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId);
getLauncherActivityInfos(packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
return false;
@@ -762,7 +796,8 @@
* <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
* launcher activities, only one of the icons is returned arbitrarily.
*/
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ String callingPackageName) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -785,7 +820,13 @@
// TODO(b/298452477) Handle monochrome icons.
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
- return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
+ Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
+ if (getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
+ == MODE_ALLOWED) {
+ icon = includeCloudOverlay(icon);
+ }
+ return icon;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 609b3aa..f09fa21 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6388,8 +6388,10 @@
}
@Override
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
- return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ @NonNull String callingPackageName) {
+ return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user,
+ callingPackageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ca00c84..88dc60c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -297,6 +297,8 @@
return runSetHiddenSetting(true);
case "unhide":
return runSetHiddenSetting(false);
+ case "unstop":
+ return runSetStoppedState(false);
case "suspend":
return runSuspend(true, 0);
case "suspend-quarantine":
@@ -2662,6 +2664,26 @@
return 0;
}
+ private int runSetStoppedState(boolean state) throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState");
+ mInterface.setPackageStoppedState(pkg, state, userId);
+ getOutPrintWriter().println("Package " + pkg + " new stopped state: "
+ + mInterface.isPackageStoppedForUser(pkg, translatedUserId));
+ return 0;
+ }
+
private int runSetDistractingRestriction() {
final PrintWriter pw = getOutPrintWriter();
int userId = UserHandle.USER_SYSTEM;
@@ -4934,6 +4956,8 @@
pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println("");
+ pw.println(" unstop [--user USER_ID] PACKAGE");
+ pw.println("");
pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]");
pw.println(" Suspends the specified package(s) (as user).");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 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/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 9780440..a185ad9 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -419,7 +419,7 @@
"installerTitle");
packageUserState.setArchiveState(archiveState);
assertEquals(archiveState, packageUserState.getArchiveState());
- assertTrue(archiveState.getArchiveTimeMillis() > currentTimeMillis);
+ assertTrue(archiveState.getArchiveTimeMillis() >= currentTimeMillis);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 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/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index ec7e359..e989d7b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -552,20 +552,22 @@
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isNull();
}
@Test
public void getArchivedAppIcon_notArchived() {
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isNull();
}
@Test
public void getArchivedAppIcon_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
- mIcon);
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isEqualTo(mIcon);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 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/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/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 0805485..81df597 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -157,6 +157,7 @@
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
case Context.ROLE_SERVICE:
+ case Context.APP_OPS_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
return getTestContext().getSystemService(name);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index ea948ca..863cda4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -118,12 +118,19 @@
// Constructors that should be used to create instances of specific classes. Overrides scoring.
private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS;
+ // Setter methods that receive String parameters, but where those Strings represent Uris
+ // (and are visited/validated).
+ private static final ImmutableSet<Method> SETTERS_WITH_STRING_AS_URI;
+
static {
try {
PREFERRED_CONSTRUCTORS = ImmutableMap.of(
Notification.Builder.class,
Notification.Builder.class.getConstructor(Context.class, String.class));
+ SETTERS_WITH_STRING_AS_URI = ImmutableSet.of(
+ Person.Builder.class.getMethod("setUri", String.class));
+
EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder()
.put(RemoteViews.class,
// b/245950570: Tries to connect to service and will crash.
@@ -257,7 +264,7 @@
@Nullable Class<?> styleClass, @Nullable Class<?> extenderClass,
@Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) {
SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context);
- Set<Class<?>> excludedClasses = includeRemoteViews
+ ImmutableSet<Class<?>> excludedClasses = includeRemoteViews
? ImmutableSet.of()
: ImmutableSet.of(RemoteViews.class);
Location location = Location.root(Notification.Builder.class);
@@ -294,7 +301,7 @@
}
private static Object generateObject(Class<?> clazz, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
if (excludingClasses.contains(clazz)) {
throw new IllegalArgumentException(
String.format("Asked to generate a %s but it's part of the excluded set (%s)",
@@ -369,7 +376,7 @@
}
private static Object constructEmpty(Class<?> clazz, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
Constructor<?> bestConstructor;
if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) {
// Use the preferred constructor.
@@ -431,7 +438,7 @@
}
private static void invokeAllSetters(Object instance, Location where, boolean allOverloads,
- boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes,
+ boolean includingVoidMethods, ImmutableSet<Class<?>> excludingParameterTypes,
SpecialParameterGenerator specialGenerator) {
for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where,
allOverloads, includingVoidMethods, excludingParameterTypes)) {
@@ -462,24 +469,34 @@
}
private static Object[] generateParameters(Executable executable, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable)
+ " in " + where);
Type[] parameterTypes = executable.getGenericParameterTypes();
Object[] parameterValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
- parameterValues[i] = generateParameter(
- parameterTypes[i],
+ boolean generateUriAsString = false;
+ Type parameterType = parameterTypes[i];
+ if (SETTERS_WITH_STRING_AS_URI.contains(executable)
+ && parameterType.equals(String.class)) {
+ generateUriAsString = true;
+ }
+ Object value = generateParameter(
+ generateUriAsString ? Uri.class : parameterType,
where.plus(executable,
String.format("[%d,%s]", i, parameterTypes[i].getTypeName())),
excludingClasses,
specialGenerator);
+ if (generateUriAsString) {
+ value = ((Uri) value).toString();
+ }
+ parameterValues[i] = value;
}
return parameterValues;
}
private static Object generateParameter(Type parameterType, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
if (parameterType instanceof Class<?> parameterClass) {
return generateObject(
parameterClass,
@@ -487,7 +504,8 @@
excludingClasses,
specialGenerator);
} else if (parameterType instanceof ParameterizedType parameterizedType) {
- if (parameterizedType.getRawType().equals(List.class)
+ if ((parameterizedType.getRawType().equals(List.class)
+ || parameterizedType.getRawType().equals(ArrayList.class))
&& parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) {
ArrayList listValue = new ArrayList();
for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) {
@@ -503,12 +521,14 @@
}
private static class ReflectionUtils {
- static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) {
- return Arrays.stream(containerClass.getDeclaredClasses())
- .filter(
- innerClass -> clazz.isAssignableFrom(innerClass)
- && !Modifier.isAbstract(innerClass.getModifiers()))
- .collect(Collectors.toSet());
+ static ImmutableSet<Class<?>> getConcreteSubclasses(Class<?> clazz,
+ Class<?> containerClass) {
+ return ImmutableSet.copyOf(
+ Arrays.stream(containerClass.getDeclaredClasses())
+ .filter(
+ innerClass -> clazz.isAssignableFrom(innerClass)
+ && !Modifier.isAbstract(innerClass.getModifiers()))
+ .collect(Collectors.toSet()));
}
static String methodToString(Executable executable) {
@@ -611,9 +631,16 @@
}
private static class SpecialParameterGenerator {
+
+ private static final ImmutableSet<Class<?>> INTERESTING_CLASSES_WITH_SPECIAL_GENERATION =
+ ImmutableSet.of(Uri.class, Icon.class, Intent.class, PendingIntent.class,
+ RemoteViews.class);
+
private static final ImmutableSet<Class<?>> INTERESTING_CLASSES =
- ImmutableSet.of(Person.class, Uri.class, Icon.class, Intent.class,
- PendingIntent.class, RemoteViews.class);
+ new ImmutableSet.Builder<Class<?>>()
+ .addAll(INTERESTING_CLASSES_WITH_SPECIAL_GENERATION)
+ .add(Person.class) // Constructed via reflection, but high-score.
+ .build();
private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of();
private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES =
@@ -637,7 +664,7 @@
}
static boolean canGenerate(Class<?> clazz) {
- return INTERESTING_CLASSES.contains(clazz)
+ return INTERESTING_CLASSES_WITH_SPECIAL_GENERATION.contains(clazz)
|| MOCKED_CLASSES.contains(clazz)
|| clazz.equals(Context.class)
|| clazz.equals(Bundle.class)
@@ -672,17 +699,6 @@
return Icon.createWithContentUri(iconUri);
}
- if (clazz == Person.class) {
- // TODO(b/310189261): Person.setUri takes a string instead of a URI. We should
- // find a way to use the SpecialParameterGenerator instead of this custom one.
- Uri personUri = generateUri(
- where.plus(Person.Builder.class).plus("setUri", String.class));
- Uri iconUri = generateUri(where.plus(Person.Builder.class).plus("setIcon",
- Icon.class).plus(Icon.class).plus("createWithContentUri", Uri.class));
- return new Person.Builder().setUri(personUri.toString()).setIcon(
- Icon.createWithContentUri(iconUri)).setName("John Doe").build();
- }
-
if (clazz == Intent.class) {
return new Intent("action", generateUri(where.plus(Intent.class)));
}